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 Cleaning up old sessions

If storageType is either "ram", "file" or "cookie", then CherryPy will automatically clean up expired sessions for you. If storageType is set to "custom", then you have to write your own code to clean up old sessions in a special function called cleanUpOldSessions.

16.6 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 dictionary that can be accessed through request.sessionMap. You can use this dictionary to store the data that you want to keep about a perticular client.

The reason why sessionMap is a member variable of request is because this makes it thread-safe.

16.7 Example

For instance, if you want to store session data in RAM, if you want sessions to expire after 2 hours, and if you want CherryPy to clean up expired sessions every hour, use the following config file (RootServer.cfg):
[session]
storageType=ram
timeout=120
cleanUpDelay=60
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.8 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 three 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> create table session_data(sessionId varchar(50), sessionData text, expirationTime int unsigned);
Query OK, 0 rows affected (0.00 sec)

mysql> describe session_data;
+----------------+------------------+------+-----+---------+-------+
| Field          | Type             | Null | Key | Default | Extra |
+----------------+------------------+------+-----+---------+-------+
| sessionId      | varchar(50)      | YES  |     | NULL    |       |
| sessionData    | text             | YES  |     | NULL    |       |
| expirationTime | int(10) unsigned | YES  |     | NULL    |       |
+----------------+------------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
Note that we've chosen to use an int column to store the expiration time, which is easier because that's how this variable is passed to the saveSessionData function. We could have chosen to use a datetime column but then we would have to convert expiration time into a suitable format to insert it into the table (and convert it back in loadSessionData).

Here is an example code that uses the session_data MySql table to store session data:

use MySql
import pickle, base64, StringIO, time
CherryClass MyDb(MySql):
function:
    def __init__(self):
        self.openConnection('localhost', 'test', '', 'test')

def saveSessionData(sessionId, sessionData, expirationTime):
    # 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', %s)" % (
        sessionId, dumpStr, int(expirationTime)))

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

def cleanUpOldSessions():
    now = time.time()
    myDb.query("delete from session_data where expirationTime < %s" % int(now))

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

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