Subsections

16. How to use sessions

16.1 Introduction to sessions

The main difference between a web application and a regular application is that in a web application, each page requested by a client is independant from the other pages requested by the same client. In other words, we have to start from scratch for each page.

Of course, for any serious application, this is not acceptable and we need to keep some data about a given user across several pages.

Let's assume for example that we want to keep the first name and the last name of the user across several pages. We ask the user for his first name and last name on the first page of the website (using a form) and we need to use that data in other pages of the site.

First of all, we have basically two options:

If we choose the first option, we have several ways to do this:

The first two options are not really handy as they force you to use extra code for each link. The third option is better but it is not very handy if we have lots of data to keep about a user.

So another option is to store the data on the server side and to just keep a pointer to that data on the client side. This is what sessions do ...

16.2 Possible implementations for sessions

The pointer to the data is usually called a sessionId. Since we need to keep the sessionId from one page to the next on the client side, we still have the same options as described in the previous section:

The session data itself (in our example: the first name and the last name) is stored on the server side. Again, there are many options to store it:

Some systems can also mix some of these options: for instance, session data can be stored in RAM and saved to disk or to a database once in a while.

Another option is to store everything in RAM and save it to disk when the server shutsdown (although this might be dangerous because we might not have time to save it if the server crashes or is killed badly).

Also, session data might not change too often so another option is to store it in RAM and save it to disk or to a database only when it changes.

16.3 Sessions implementation in CherryPy

The following options are supported in CherryPy:

You should use the first option (RAM) if you don't use multiple processes (using multiple threads is fine) and if you don't care about losing session data when the server is stopped/restarted.

You should use the second option (filesystem) if you don't have a database and don't want to lose data.

The third option is quite interesting but only works if the session data for each user is quite small (some browsers limit the size of cookies to 4KB).

The fourth option (storing your session data in a database) is the recommended option for most "real-world" websites.

16.4 Configuration variables used to control sessions

In order to use sessions, you must first enable sessions by setting a few configuration variables on the config file, under the [session] scope. The new configuration options are:

16.5 Using sessions in your code

Once you have enabled sessions in the config file, the way it works is very easy: CherryPy just makes available for you a global variable called sessionMap, which is in fact a dictionary where you can store your data and retrieve it in another page.

16.6 Example

For instance, if you want to store session data in RAM and if you want sessions to expire after 2 hours, use the following config file (RootServer.cfg):
[session]
storageType=ram
timeout=120
If you want to store session data to disk (in a directory called /home/user/sessionData), use the following config file:
[session]
storageType=file
storageFileDir=/home/user/sessionData

The following example is a trivial page view counter:

CherryClass Root:
mask:
    def index(self):
        <py-code="
            count=sessionMap.get('pageViews', 0)+1
            sessionMap['pageViews']=count
        ">
        <html><body>
            Hello, you've been here <py-eval="count"> time(s)
        </body></html>

16.7 Storing session data in a database (or anywhere else)

From CherryPy-0.9-gamma and later, you can now write your own custom functions to save/load session data. This allows you to store them wherever you want. In order to do so, all you have to do is declare the two following special functions somewhere in your code:

The following example shows how to store the session data in a MySql database. It assumes that there is a table called "session_data":

mysql> describe session_data;
+-------------+-------------+------+-----+---------+-------+
| Field       | Type        | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| sessionId   | varchar(50) | YES  |     | NULL    |       |
| sessionData | text        | YES  |     | NULL    |       |
+-------------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
Here is an example code that uses the session_data MySql table to store session data:
use MySql
import pickle, base64, StringIO
CherryClass MyDb(MySql):
function:
    def __init__(self):
        self.openConnection('localhost', 'test', '', 'test')

def saveSessionData(sessionId, sessionData):
    # Pickle sessionData and base64 encode it
    f = StringIO.StringIO()
    pickle.dump(sessionData, f, 1)
    dumpStr = f.getvalue()
    f.close()
    dumpStr = base64.encodestring(dumpStr)
    myDb.query("delete from session_data where sessionId='%s'" % sessionId)
    myDb.query("insert into session_data values('%s', '%s')" % (
        sessionId, dumpStr))

def loadSessionData(sessionId):
    dumpStrList = myDb.query("select sessionData from session_data where sessionId='%s'" % sessionId)
    if not dumpStrList:
        return None
    dumpStr = base64.decodestring(dumpStrList[0][0])
    f = StringIO.StringIO(dumpStr)
    sessionData = pickle.load(f)
    f.close()
    return sessionData

CherryClass Root:
view:
    def index(self):
        i = sessionMap.get('counter', 0) + 1
        sessionMap['counter'] = i
        return str(i)

See About this document... for information on suggesting changes.