So far we have been saving all application state at the browser inside hidden fields. Sometimes it is preferable to retain state at the server side. Albatross includes support for server-side sessions in the SessionServerContextMixin and SessionServerAppMixin classes. In this section we will modify the popview.py program to use server-side sessions.
The SessionServerAppMixin class uses a socket to communicate
with the session-server/al-session-daemon session server.
You will need to start this program before using the new
popview
application.
In previous versions of the program we were careful to place all user response into forms. This allowed Albatross to transparently attach the session state to hidden fields inside the form. When using server-side sessions Albatross does not need to save any application state at the browser so we are free to use URL style user inputs. To illustrate the point, we will replace all of the form inputs with URL user inputs.
The complete sample program is contained in the samples/popview3 directory. Use the install.py script to install the sample.
cd samples/popview3 python install.py
The new list.html template file follows.
<html> <head> <title>Message List</title> </head> <body> <al-for iter="m" expr="mbox" pagesize="15" prepare/> <al-if expr="m.has_prevpage()"><al-a prevpage="m">Prev</al-a> | </al-if> <al-a href="refresh=1">Refresh</al-a> <al-if expr="m.has_nextpage()"> | <al-a nextpage="m">Next</al-a></al-if> <hr noshade> <table> <tr align="left"> <td></td> <td><b>To</b></td> <td><b>From</b></td> <td><b>Subject</b></td> </tr> <al-for iter="m" continue> <tr align="left" valign="top"> <td align="right"> <al-a expr="'msgnum=%s' % m.value().msgnum"> <al-value expr="m.value().msgnum"></al-a> </td> <td><al-value expr="m.value().hdrs['To']"></td> <td><al-value expr="m.value().hdrs['From']"></td> <td><al-value expr="m.value().hdrs['Subject']"></td> </tr> </al-for> </table> <hr noshade> </body> </html>
Next the new detail.html template file.
<html> <head> <title>Message Detail</title> </head> <body> <al-a href="list=1">Back</al-a> <hr noshade> <table> <al-for iter="f" expr="('To', 'Cc', 'From', 'Subject')"> <al-if expr="msg.hdrs[f.value()]"> <tr align="left"> <td><b><al-value expr="f.value()">:</b></td> <td><al-value expr="msg.hdrs[f.value()]"></td> </tr> </al-if> </al-for> </table> <hr noshade> <pre><al-value expr="msg.body"></pre> </body> </html>
One of the more difficult tasks for developing stateful web applications is dealing with browser requests submitted from old pages in the browser history.
When all application state is stored in hidden fields in the HTML, requests from old pages do not usually cause problems. This is because the old application state is provided in the same request.
When application state is maintained at the server, requests from old pages can cause all sorts of problems. The current application state at the server represents the result of a sequence of browser requests. If the user submits a request from an old page in the browser history then the fields and values in the request will probably not be relevant to the current application state
Making sure all application requests are uniquely named provides some protection against the application processing a request from another page which just happened the share the same request name. It is not a complete defense as you may receive a request from an old version of the same page. A request from an old version of a page is likely to make reference to values which no longer exist in the server session.
Some online banking applications attempt to avoid this problem by opening browser windows that do not have history navigation controls. A user who uses keyboard accelerators for history navigation will not be hindered by the lack of navigation buttons.
The popview application does not modify any of the data it uses so there is little scope for submissions from old pages to cause errors.
By changing the base class for the application object we can gain support for server side sessions. Albatross includes a simple session server and supporting mixin classes.
The new application prologue looks like this:
#!/usr/bin/python from albatross import SimpleSessionApp, SessionAppContext from albatross.cgiapp import Request import popviewlib
The execution context now inherits from SessionAppContext:
class AppContext(SessionAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body()
And the new application class looks like this:
class App(SimpleSessionApp): def __init__(self): SimpleSessionApp.__init__(self, base_url = 'popview.py', template_path = '.', start_page = 'login', secret = '-=-secret-=-', session_appid = 'popview3') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self)
The session_appid argument to the constructor is used to uniquely identify the application at the server so that multiple applications can be accessed from the same browser without the session from one application modifying the session from another.
Apart from changes to load the new template files, we also need to change the ListPage class because we changed the method of selecting messages from the message list.
class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('msgnum'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html')