How-To: Easy mod_python Session Handling with pso.session


Version: $Revision: 1.7 $
Author: thanos vassilakis
Company: Script Foundry Inc.
Document URL: http://sourceforge.net/docman/display_doc.php?docid=10174&group_id=49265
Contact: thanos@scriptfoundry.com
Feed-back: pso-development@lists.sourceforge.net
Copyright: thanos vassilakis 2001,2002
Contributions: Ming Huang and Vadim Shaykevich.
Last Changed: $Header: /cvsroot/pso/doc/session-modpython.html,v 1.7 2003/01/03 17:25:06 thanos Exp $



  1. Introduction
  2. System Requirements
  3. Real Quick Example - 95% of the Time
  4. What's Going On
  5. More Control - 5% of the Time

  6. Session classes
  7. Installation
  8. Installation Example
  9. Mentioned Urls

  • Introduction:

    This document shows you how to use pso.session to add easy session handling to a mod_python request handler. Python Service Objects is a open source internet service development system. Its session module is ideal for use with mod_python, and in fact included in this package is ModPythonRequest which is the mod_python implementation bridge. The pso system allows you to develop platform and server independent request handlers.

  • System Requirements

  • Real Quick Example - 95% of the Time

    In the mod_python installation guide a simple test handler is given: mptest.py
    Its source is as follows:
    
    from mod_python import apache
    
    def handler(req):
    	req.send_http_header()
    	req.write("Hello World!")
    	return apache.OK
    
    It's .htaccess file is:
    
    AddHandler python-program .py
    PythonHandler mptest 
    

    test: http://www.0x01.com/~thanos/pso/tests/session/modp/mptest.py

    Now we will add the pso.session handling by just adding two lines to the original .htaccess file:
    
    AddHandler python-program .py
    PythonHandler quicktest
    PythonFixupHandler pso.modpython::fixup
    PythonLogHandler pso.modpython::cleanup
    
    The PythonFixupHandler pso.modpython::fixup adds a pso service object to the request object. The PythonFixupHandler pso.modpython::cleanup removes the pso service object from the request object. In the test handler we need not make any changes, but to demo the use of the pso.session we we added a few lines:
    
    from mod_python import apache
    
    def handler(req):
    	req.send_http_header()
    	try:
    		req.pso().session['visits'] +=1
    	except:
    		 req.pso()Session['visits'] =1
    	req.write("Hello World!  ~ Your visit number: %(visits)d ~ Try Reload !" %  req.pso()Session)
    	req.write("session: %s" %  req.pso()Session__dict__)
    	return apache.OK
    
    The above example was very quick. A lot of default values were used and many assumption were made, yet it shows you how painless it is to add and use session handling.

    test: http://www.0x01.com/~thanos/pso/tests/session/quick/quicktest.py

  • What's Going On

    fixup is a simple wrapper around pso's ServiceHandler.
    
    def fixup(req, requestImpl= ModPythonRequest, sessionImpl=None): 
    	sys.stdout = req.pso = requestImpl(req)
    	session = req.pso().getSession( sessionImpl)
    	req.pso = ServiceHandler(req)
    	session = req.pso().getSession( sessionImpl)
    	return apache.OK
    
    The algorithm for pso.session.ServiceHandler.getSession is the following:
    1. Get the service id from req.subprocess_env. Default to the value "host.module name".
    2. Get the "Cookie" entry from req.headers_in.
    3. Extract from the cookie the value keyed on the service id and use it as the session id.
    4. If no session id was found generate a new one.
    5. Try and load and unpickle the session file whose name is based on the service id and session Id.
    6. If this fails
      1. create a new session
      2. add to req.headers_out['cookie-set'] the value serviceId=sessionId, for example: req.headers_out.add('cookie-set','MI6Service=007;').
      3. Save session when req.pso() is finalized.

  • More Control - That Last 5%

    Setting the service id:
    add the line
    PythonOption PSOServiceId 'Your_Service_Id'
    to the .htaccess file or to the relevant apache httpd.conf <directory> block.

    Forcing the session id:
    add the line
    PythonOption 'Your_Service_Id' 'Your_Session_Id'
    to the .htaccess file or to the relevant apache httpd.conf <directory> block.

    Setting when a session expires
    By default sessions will expire when the user closes their browser. You can preset the life span of a session for the service in question by adding in the .htaccess file the following line:
    PythonOption PSOSessionExpire 900 # 15 minutes
    You can use simple expressions
    PythonOption PSOSessionExpire "15*60" # 15 minutes
    You can also set the sessions to expire on a specific date:
    PythonOption PSOSessionExpire "Mon, 15 Apr 2001 19:00:00 GMT" # 15 minutes
    For more on this format see RFC2068 section 3.3.1 [also RCF822 and RCF1123] If a date is not give all values are seconds relative to the present. You can also control the session's lifespan in your code:
     
    • When the browser closes (the default):
       session.expire()
    • Right away:
      session.expire(0) 
      

      test: http://www.0x01.com/~thanos/pso/tests/session/expire/mptest.py

    • In a given time:
      session.expire(15*60) # or PythonOption PSOSessionExpire "60*15" etc.
      twoDays = 60*60*48
      session.expire(date = time()+twoDays)
    • On a given day
      import time
      #PythonOption PSOSessionExpireOn "31-12-2002"
      session.expire("Mon, 15 APR 2001 19:00:00", "%a, %d %b %Y %H:%M:%S")

    Reverting the current session to its previous state:
    session.revert()

    Explicitly saving the current session:
    session.save()

    Storing the session files in another path:
    There are several ways of doing this:
    • The easy way is to add the line
      PythonOption PSOSessionFileLoader_Path "/tmp/path0/"# remember the trailing '/'
      to the .htaccess file or to the relevant apache httpd.conf <directory> block.

      Test: http://www.0x01.com/~thanos/pso/tests/session/path0/mptest.py

    • For the oo fans you can subclass pso.session.CookeFileImpl overloading the attribute CookieFileImpl.PATH and then write your own fixup.
      
      # file: path1test.py
      #
      from mod_python import apache
      from pso import session, modpython 
      
      class MyFileImpl(session.CookieFileImpl): 
      	PATH="/tmp/path1/"
      
      def fixup(req):
      	modpython.fixup(req, sessionImpl=MyFileImpl)
      	return apache.OK
      .
      .
      .
      
      Now change the PythonFixupHandler in the .htaccess (or httpd.conf) entry to:
      
      #in .htaccess
      #
      PythonHandler path1test
      PythonFixupHandler path1test::fixup
      

      test: http://www.0x01.com/~thanos/pso/tests/session/path1/path1test.py

    • For more control you can also overload the method CookieFileImpl.getPath
      
      # file: path2test.py
      #
      from mod_python import apache
      from pso import session, modpython
      
      class MyFileImpl(session.CookieFileImpl): 
      	def getPath(self, reqHandler, session):
      		"""
      		you must return the full session file path excluding name.
      		It must be  valid, and the handler must have the 
      		authority to create or write to the given file.
      		"""
      		return	"/tmp/path2/"
      
      def fixup(req):
      	modpython.fixup(req, sessionImpl=MyFileImpl)
      	return apache.OK
      .
      .
      .
      
      Again remember change the PythonFixupHandler entry to:
      
      #in .htaccess
      #
      PythonHandler path1test
      PythonFixupHandler path2test::fixup
      

      test: http://www.0x01.com/~thanos/pso/tests/session/path2/mptest.py

    
            

    Storing the session in a database
    This is yet another OO example. Just create your own implmentaion class and mix it in with SessionImpl. The code below is a bit simplistic, but illustrates the basic idea, and works!
    
    #file: mysql/mysqltest.py
    #
    from mod_python import apache
    from pso import session, modpython
    import pickle 
    import base64
    from time import time
    import MySQL
    
    
    class MySQLLoader:
    	dbconn = MySQL.connect("localhost", "test")
    	dbconn.query('use test')
    	def load(self, reqHandler, session):
    		" must return the saved session obj or None. "
    		result = self.dbconn.query("SELECT sessionObj FROM session WHERE sessionId='%s';" %  self.getSessionId(reqHandler))
    		rows = result.fetchrows()
    		if rows:
    			sessionData = rows[0][0]
    			if sessionData:
    				return pickle.loads(base64.decodestring(sessionData))
    				return session
    
    	def save(self, reqHandler, session):
    		serviceId = self.getServiceId(reqHandler)
    		sessionId = self.getSessionId(reqHandler)
    		sessionStr = base64.encodestring(pickle.dumps(session,1))
    		res = self.dbconn.query("UPDATE session SET sessionObj='%s', serviceId='%s' WHERE sessionId='%s';" % (sessionStr,  serviceId, sessionId))
    		if res.affectedrows() == 0:
    			self.dbconn.query("INSERT INTO session (serviceId,sessionId, sessionObj) values ('%s','%s','%s')" % (serviceId, sessionId, sessionStr))
    
    	def newSessionId(self, reqHandler):
    			"returns a new id"
    			
    			result = self.dbconn.query("SELECT count(sessionId) FROM session;")
    			rows = result.fetchrows()
    			if rows:
    				int(rows[0][0])				
    			return 0
    		
                             
    class MySQLImpl(session.CookieSession, MySQLLoader, session.SessionImpl): pass
                             
    
    def fixup(req):
    	modpython.fixup(req, sessionImpl=MySQLImpl)
    	return apache.OK
    .
    .
    .		
    
    And remember to change the PythonFixupHandler entry in .htaccess file to:
    
    PythonHandler mysqltest
    PythonFixupHandler mysqltest::fixup
    

    test: http://www.0x01.com/~thanos/pso/tests/session/mysql/mysqltest.py

    Doing things without cookies
    By default the sessionId is stored as a cookie. To match a request with a visitor you need to send an id to the visitor's browser and have it return on the next request. Cookies accomplish this. Another method is to embed in all the urls that require sessions a session id. The advantage is that if a visitor has blocked cookie acceptance, you can still operate session data between requests. The problem with this approach is that the session dies between visits. Probably the best is to use both approaches. Many sites including Amazon and Hotmail do this, and it gives them the power to give you a pretty good service, with shopping cart and all, even when you have blocked their cookies. To make things easier pso.request.ServiceRequest.serviceUrl has been implemented.

    I want to set the session cookie's path and domain
    The easiest way is to set them in the the .htaccess or httpd.conf files. The following directives are recognized by the standard session implementation class:
    • PSOCookieSession_Comment is equivalent to Comment
    • PSOCookieSession_Domain is equivalent to Domain
    • PSOCookieSession_Max-Age is equivalent to Max-Age
    • PSOCookieSession_Path is equivalent to Path
    • PSOCookieSession_Secure is equivalent to Secure
    • PSOCookieSession_Version is equivalent to Version
    • PSOCookieSession_expires is equivalent to expires?... used by Netscape et al.
    The above cookie attributes are defined in RCF2109, except for expires. expire takes a string date such as "Mon, 12 Nov 2002 13:04:56 GMT", as defines in RFC2068 section 3.3.1 [also RCF822 and RCF1123]

    What else can I do with pso
    • Using the pso system you can write one request handler that can be invoked as a mod_python handler or a cgi without modification.
    • req.pso() returns a pso service handler. The handler acts as a bridge or proxy that tries to simplify many typical web service actions, and abstract then from server content delivery system. Many methods are offered covering the flowing areas:
      • getting form input
      • browser HTTP header information
      • setting http headers
      • setting http cookies
      • file uploads
      • redirecting
      • setting return status
      • output
      • logging
      For more on this see the pso doc.

      If we take our above quick example:

      
      
      from mod_python import apache
      
      def handler(req):
      	req.send_http_header()
      	try:
      		req.pso().session['visits'] +=1
      	except:
      		req.pso().session['visits'] =1
      	req.write("Hello World!  ~ Your visit number: %(visits)d ~ Try Reload !" %  req.pso().session)
      	req.write("session: %s" %  req.pso()Session__dict__)
      	return apache.OK
      

      Some simple changes can make it run as mod_python or as cgi

      
      
      #!/usr/bin python2.2
      
      from pso.session import ServiceHandler, OK
      	
      
      def handler(req):
      	req.pso().send_http_header)_
      	try:
      		req.pso()Session['visits'] +=1
      	except:
      		req.pso()Session['visits'] =1
      	print "Hello World!  ~ Your visit number: %(visits)d ~ Try Reload !" %  req.pso()Session
      	print "session: %s" %  req.pso()Session__dict__
      	return OK
      
      if __name__ =='__main__':
      	ServiceHandler().run(handler)
      
      
      The real differences are:
      1. If you want your handler to run on a server where mod_python has not been installed, you have to import the return codes from pso.service.
      2. For the handler to run as a script, you add a __main__, and invoke your handler through ServiceHandler(handler)Run().
      3. All output should go through the req.pso()Write method that is available for both mod_python and CGI.
      For more on this see the mini HOW-TO: Writing one request handler for both CGI or mod_python. and the sister of this document Easy CGI Session handling with pso.session.

  • pso.session classes -- see http://www.scriptfoundry.com/modules/pso/

  • Installation:

    pso should be installed using Distutils which comes standard with python 1.6. and up
    1. Download the distribution file at http://www.scriptfoundry.com/modules/dist/
    2. Uncompress the distribution file
    3. Change into the directory created during the uncompression of distribution file
    4. execute the command:
      python setup.py install

  • Installation Example
    1. create your project directory:
      $ mkdir ~/public_html/psotest
    2. Download and un-tar the latest pso package:
      $ wget  http://www.0x01.com/~thanos/pso/dist/current.tgz
      or for MS lovers: [ http://www.0x01.com/~thanos/pso/dist/current.exe]
      $ tar zvfx current.tgz
      	
    3. cd into the pso directory and then install it locally into your psotest directory.
      $ cd  pso-XX/
      $ python2.1 setup.py install --install-purelib=~/public_html/testpso
      
    4. Create a .htaccess file with the following:
      #.htaccess for testing pso using CGI
      #
      # Options ExecCGI directs apache to allow cgi's to be run from this directory.
      # AddHandler cgi-script  .py  treat any .py file as a script
      #SetEnv PSOServiceId MyPSOTest  names  session cookie as MyPSOtest
      
      Options ExecCGI Indexes 
      AddHandler cgi-script  .cgi 	
      SetEnv PSOServiceId MyPSOTest	
      
    5. Now we will create a test handler and check that it works, we will call the file test.py , but creat a link to it test.cgi. [We are doing this link, and setting the Options Indexes and AddHandler cgi-script .cgi for only one reason: to allow you to view and browser the source code from the web.]
      #!/usr/bin/env python2.1
      #
      from pso.service import ServiceHandler
      def testHandler(serviceRequest):
      	print "hello world"
      
      if __name__ == '__main__':
      	ServiceHandler().run(testHandler)
      
      Now we will set the file permissions, and do the link:
      $ chmod a+x  test.py
      $ ln test.py test.cgi
      
      and try it from the command line, should give you this:
      python:~/public_html/testpso# ./test.cgi
      set-cookie: SESSION_ID=@60123.0SESSION_ID;
      content-type: text/html
      
      hello world
      python:~/public_html/testpso# 
      
      Now lets try it using a browser: http://www.yourhost.com/~yourid/testpso/test.cgi

    see Greg Ward's http://www.python.org/doc/current/inst/inst.html If all else fails. Just copy the pso directory somewhere in the python PATH or to the directory of your request handler.

  • www.python.org http://www.python.org/
    Gregory Trubetskoy's mod_python http://www.modpython.org/
    RCF2109 http://www.faqs.org/rfcs/rfc2109.html
    RFC2068 http://www.faqs.org/rfcs/rfc2068.html
    RCF822 http://www.faqs.org/rfcs/rfc822.html
    RCF1123 http://www.faqs.org/rfcs/rfc1123.html
    Greg Ward's intalling python modules http://www.python.org/doc/current/inst/inst.html
       

    SourceForge Logo