Subsections
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:
- keep the first name and the last name from one page to the next on the client side
- store the first name and the last name somewhere on the server side, and only keep
a pointer to that data on the client side: this is what sessions do
If we choose the first option, we have several ways to do this:
- Keep the first name and the first name in the URL: each time there is a link to another page, we could add the
following arguments to the URL: "firstName=....&lastName=..."
- Keep the data in hidden fields of a form: in each page, we could have a form with two hidden fields like this:
<form method="post" name="myForm" action="dummy">
<hidden name="firstName" value="...">
<hidden name="lastName" value="...">
</form>
Everytime there is a link to another page, we have to submit the form to get the data. We can use something like this:
<a href="#"
onClick="
document.myForm.action='...put link here...';
document.myForm.submit();
return false;">...</a>
- Store the first name and the last name in a cookie
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 ...
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:
- Store it in RAM
- Advantage: very fast; can store any python object
- Disavantage: doesn't work in in a multi-process environment; we lose the data when the server is stopped
- Store it in the filesystem
- Advantage: never lose data; works in a multi-process environment
- Disavantage: slow; lots of reads/writes to disk; can only store pickable python objects
- Store it in a cookie in the client's browser
- Advantage: works in multi-thread or multi-process environments. Server is not vulnerable to attacks that consist in artificially starting thousands of sessions on the server to bring it to its knees.
- Disavantage: can only store pickable python objects. Session data must be quite small (some browsers limit the size of cookie data to 4KB)
- Store it in a database
- Advantage: never lose data; works in a multi-process environment
- Disavantage: slower than RAM; lots of reads/writes to database; can only store pickable python objects
- Store it anywhere you can think of ...
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.
The following options are supported in CherryPy:
- sessionId: for now, the sessionId is always stored in a cookie
- session data: three options are build in and you can implement very easily any storage method you want ...
- Store everything in RAM; never save it to disk or to a database
- Store everything in the filesystem; read and save the data for each request
- Store the session data in the client browser, as a cookie
- Write your own storage functions so you can store the data wherever you want
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.
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:
- storageType: Can be either "ram", "file", "cookie" or "custom": this tells whether you want to store session data in RAM, to disk, in a cookie, or write your own storage functions.
- storageFileDir: This must be set if you set storageType to "file". Set it to the directory where the session
data will be stored
- timeout: Number of minutes after which a session expires if there was no activity. The default is 60 minutes.
- cleanUpDelay: If cleanUpDelay is set to N minutes, then the CherryPy server will check every N minutes if there are old/expired sessions that need to be cleaned up. The default is 60 minutes.
- cookieName: Name of the cookie that stores the sessionId: The default is "CherryPySession"
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.
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.
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>
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:
- saveSessionData(sessionId, sessionData, expirationTime): Store the sessionData and expiration time for this sessionId somewhere. expirationTime is a float as returned by the time.time() function.
- loadSessionData(sessionId): Retrieve the sessionData and expiration time for this sessionId and returns them as a tuple
- cleanUpOldSessions(): Delete expired sessions
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.