4.5 Adding Server-Side Session Support to Popview
Description: Adding Server-Side Session Support to Popview
Keywords: albatross

#
4.5 Adding Server-Side Session Support to Popview

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')