|
|
||||||||||||||||||
|
|
||||||||||||||||||
![]() |
![]() |
Issue 2 - Revision 4 / January 16, 2003
|
|||
|
Using Zope Sessions Zope's new sessions support is not only easy to use but it is also well designed and extensible - - - - - - - - - - - - By Michel Pelletier | April 8, 2002 Using Zope Sessions For a while, one of the most requested features for Zope was session support. Seemingly a simple task, and something that almost all other platforms offered as a basic feature, it's lack is Zope was a sore that went unhealed for quite a while. Zope Corporation has, however, fixed that feature hole in recent (as of Zope 2.5) versions of Zope. Zope's new sessions support is not only easy to use but it is also well designed and extensible, but we'll get into that later on. First, and introduction. As many of you may know, the HTTP protocol is stateless. That means that a request comes from a client, goes to the server, and the server sends a response, and then it's over. No information is kept between requests from the client to the server, there is no state that is maintained between requests. This feature makes HTTP very simple, amazingly lightweight and robust, but at a cost. The cost is that sometimes you need to keep track of state between web requests. The most common example is an online "Shopping Cart". You want your customers to be able to browse your catalog, choosing items to buy over a long period of time, over the course of many different requests. Between requests, you don't want to "forget" what your customers want to buy. Without sessions, remembering that information can be a hard problem. There are three fundamental problems to solve:
To track a user you can store a cookie on your users web browser, but some people may turn off cookies or not trust your cookie. And the problem is more than just something cookies can solve, you would need to write lots of "cookie code" to handle the tracking of a session. Storage isn't such a big deal in Zope, but Zope's object database usually works by appending new values of objects in the database so that you can undo changes. Clearly you don't need this feature for session data, at which point the problem gets harder, you can store the information in a SQL database, but that makes it much more complex. Cleaning up is a big issue. Because HTTP is stateless there is no "hang-up" signal when the user simply closes their browser to "end" their session. How long is session data valid? What happens to the session data when it goes stale? Zope's session support solves all of these problems for you, and for the most part you don't have to think of any of them when you use Zope sessions. Zope provides you with a session "object" that works just like a Python dictionary. In this dictionary you can store any kind of information you want to remember about your user as they use your system. The Zope session components take care of tracking users, storing data, and invalidating old session data automatically for you. Zope 2.5 and up comes with an Examples folder in the root folder that contains a ShoppingCart folder. In this folder you will find a very simple shopping cart application that you can learn from and extend to suit your needs. In this article, we'll go over the basic features of this example and explain some of the architecture behind Zope sessions along the way. The first step is to use the example to get an idea of what it does. In the ShoppingCart folder, click on the View tab. Now, you are looking at what the ShoppingCart example looks like. Here you can see that you have the choice of adding different quantities of three different kinds of bird-feeding paraphernalia. Go ahead and typing in some quantities and click Add Items. You can see that the web page remembers who you are and how many of each item you want, even if you browse the page several different times.
So, you can see that the upper "half" of the screen shows you want is in your shopping cart, and the lower "half" of the screen shows you items that you can add to the shopping cart. To begin understanding how sessions work, let's first take a look at the Zope Page Template object index_html in the ShoppingCart folder. Instead of showing you the whole template here, I'm just going to start with the code that generate the upper half of the page: Click for CODE example There are quite a few dynamic elements to this half of the template, and I don't want to spend most of this article teaching the intricacies of Page Templates, so I'll breeze over the stuff not related to sessions. The first thing to notice is the tal:define statement on the first form tag:
<form action="deleteItems"
tal:define="global format_money
nocall:modules/Products/PythonScripts/standard/dollars_and_cents;
global current_items here/currentItems"
tal:condition="current_items">
This statement defines two variables, format_money and current_items. The first variable, format_money, is just a helper function that comes from the Zope standard module. The second variable defined is current_items. This variable comes from the here/currentItems Python Script that we'll look at later. This script returns the items that are in the users current session. Notice that both of these variables are "global". This means that they apply to the entire template, not just the contents of the form tag. The next important element is the tal:condition attribute on the same form tag shown above. This basicly says, if there are no items in the users session, then don't display this form at all, but if there are items in the variable current_items, then do display the form. Next, the tal:repeat attribute on the second row of the table repeats a table row for each item in the sequence 'current_items':
<tr valign="top" class="small"
tal:repeat="item current_items">
<td><input type="checkbox" name="ids:list"
value="id" tal:attributes="value item/id"></td>
<td tal:content="item/title">Item</td>
<td tal:define="price item/price"
tal:content="python:format_money(price)">$10.00</td>
<td tal:content="item/quantity">2</td>
</tr>
Each of these items in turn is given an HTML row and that row is filled in with the item's id, title, price and quantity attributes. Obviously, the important part of this template is the calling of the here/currentItems Python Script. This script does all the work of retriving the users session, so let's look at that next:
"""
Returns a list of items that are in the shopping cart.
Each item has a 'quantity' field as well as the normal
'id' and 'description' fields.
"""
session=context.REQUEST.SESSION
quantities=session.get('items', {})
items=[]
for id, quantity in quantities.items():
item=container.getItem(id)
item['quantity']=quantity
items.append(item)
return items
Here the first two lines of this script show you just how easy Zope sessions are to use:
session=context.REQUEST.SESSION
quantities=session.get('items', {})
The first line binds the short name session to the current session object, which is stored in context.REQUEST.SESSION. The second line gets the items data from the session. The last argument to the get method is an empty python dictionary. This tell get that if no items key is defined in the session, to just return an empty dictionary. To give you an idea what this quantities dictionary looks like in use, consider the following session where someone has purchased a couple of the bird feed related products in this example. The dictionary in their session may look like this:
{'510-007': 3, '510-122': 1, '510-115': 99}
Here this particular user's session says that they want a quantity of 3 items with the id 510-115, one with the id 510-115, and 99 of the id 510-115. I got this result by creating the following Python Script in the ShoppingCart example folder:
print context.REQUEST.SESSION.get('items', None)
return printed
Create this script, add a few items to your cart using the View tab and then test this script to take a look at what is stored in side your shopping cart. The for loop in the currentItems script iterates over the user's current items (which is stored in their session) and looks up the actual item in the bird feeder catalog by calling getItem. Here, you start to see the advantages that good software design brings this example. Because getItem separates the storage of items from the storage of session information, you can swap in a different getItem that does something completly different to get your item information, like querying a relational database by making getItem a Z SQL Method (or a Python Script that calls a Z SQL Method). In this example, getItem looks like this: Click for CODE example The getItem script keeps all of the items in a static dictionary, but that's ok, this is just an example, you can see how getItem can be extended easily to get items from somewhere else like a database. There are two more Python Scripts in the ShoppingCart folder worth looking at, addItems and deleteItems. addItems is called when you select items from the HTML form in the user interface and add more items to your shopping cart:
session=context.REQUEST.SESSION
# get the items, items is a dictionary that
# maps item ids to quantities
items=session.get('items',{})
for order in orders:
quantity=int(order.quantity)
if quantity != 0:
items[order.id]=items.get(order.id, 0) + quantity
# save items back to session (this is necessary
# since items is a mutable non-persistent object)
session['items']=items
# if called from the web return a response page
if REQUEST is not None:
return container.index_html(REQUEST)
This script is pretty straight forward. First, the script accepts the paramater orders, which is a sequence of Zope "record" objects that are built up from the HTML form on the main page. When the form is submitted, this method is called and the quantity and id of any items the user selected to add to the cart is marshalled into the orders parameter. The orders variable is then iterated over and stored in the users session. While this script is fine for an example, be careful using it in a production envirionment, because a maliscious user could fake a request submiting invalid order ids or quantities. In real world production, you would want to verify that the data the user submitted is valid. In this case this could be done by calling getItem and verifying that all the ids submitted by the user are valid. The last script to look at is deleteItems. It's also pretty straightforward:
session=context.REQUEST.SESSION
# get the items
items=session.get('items',{})
# remove the items
for id in ids:
del items[id]
# save changes back to the session
session['items']=items
# if this script is called from the web return a response page
if REQUEST is not None:
return container.index_html(REQUEST)
Now that you know how to use Zope's sessions, let's take a look at how sessions work under the hood. When Zope 2.5 was released, those of use who have used Zope in the past may have noticed that a few new default objects come with Zope to support session. In the root folder are three new object, browser_id_manager, session_data_manager, and temp_folder. Remember above I said that sessions involved three key problems, 1) tracking users, 2) managing and cleaning up data, and 3) storing data. These three new object in Zope take care of these three tasks. The browser_id_manager is the Zope component that tracks your users. It uses either HTTP cookies or forms to uniquely track each user that visits your site::
The session_data_manager object manages session data between the browser_id_manager and where the data gets stored. You can see from it's managment interface that there are only a few knobs, here is where you can configure the name of the session object in the request (as you can see, in the case of our example above it is SESSION) and where the data gets stored::
Last is the new folder folder object temp_folder. This is of the meta-type Temporary Folder. These new kinds of folders hold object just like normal folders, but the data goes away when Zope is stopped or restarted. Since this data does not get written to Zope's object database, high session activity will not cause your database to "bloat". Lastly, inside the temp_folder folder you will find a session_data object. This object is where the session data is actually stored. This object is used by the session_data_manager object to store session data. Because this object is inside a Temporary Folder, the session data is not persistent. Zope's new session support is immediately useful to Zope users around the world, and the design is pretty modular. While no different kinds of session managers or storage type are know to me, theoretically you could design your own kind of browser or session manager and replace the stock ones that Zope comes with. While I don't think that this is going to be something that will need to be done very often, it's nice to know that it can be done. The last issue to discuss is the documentation of session in Zope. The 2.5 release of the Zope Book includes session documentation and all session objects have help screens. The book documentation is good, but the help screens are a bit thin and some of them don't mention features that are clearly visible to the objects. For example, doing sessions with forms instead of cookies is not documented at all, so you may need to crack the source a bit to find out how that works, or perhaps we'll do another article in the near future on using cookie vs. form based session support.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ZopeMag is committed to bringing you the best in Zope Documentation. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
Reproduction of material from any of ZopeMag's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 ZopeMag |
|