Commits

Anonymous committed 9f1aeb3

new session filter (stay tuned for docs), new config.getAll function

Comments (0)

Files changed (16)

cherrypy/_cpconfig.py

     'session.cleanUpDelay': 60,
     'session.cookieName': 'CherryPySession',
     'session.storageFileDir': '',
+    
+    'sessionFilter.on': True,
+    'sessionFilter.timeout': 60,
+    'sessionFilter.cleanUpDelay': 60,
+    'sessionFilter.storageType' : 'ram',
+    'sessionFilter.cookieName': 'CherryPySession',
+    'sessionFilter.storageFileDir': '.sessionFiles',
+    
+    'sessionFilter.new': 'sessionMap', # legacy setting
     }
 configMap = {"/": defaultGlobal.copy()}
 
             autoreload.reloadFiles.append(file)
         _load(file)
 
-def get(key, defaultValue=None, returnSection=False):
+def get(key, defaultValue=None, returnSection=False, startPath = None):
     # Look, ma, no Python function calls! Uber-fast.
+    # start path lest you overload the starting search path (needed by getAll)
+    
     global cpg
     if not cpg:
         import cpg
     
-    try:
-        path = cpg.request.path
-    except AttributeError:
-        path = "/"
+    if startPath:
+        path = startPath
+    else:
+        try:
+            path = cpg.request.path
+        except AttributeError:
+            path = "/"
     
     while True:
         if path == "":
         return path
     else:
         return result
+        
+import os.path
+
+def getAll(key):
+    """
+    getAll will lookup the key in the current node and all of its parent nodes,
+    it will return a dictionary paths of each node containing the key and its value
+
+    This function is required by the session filter
+    """
+    path = get(key, None, returnSection = True)
+    value = get(key)
+    
+    result = {}
+    while value != None and path != '/':
+        result[path]= value
+        path = os.path.split(path)[0]
+        value = get(key, None, returnSection = False, startPath = path)
+        path  = get(key, None, returnSection = True, startPath = path)
+    
+    if path == '/' and value != None:
+        result[path] = value
+    
+    return result
 
 class CaseSensitiveConfigParser(ConfigParser.ConfigParser):
     """ Sub-class of ConfigParser that keeps the case of options and

cherrypy/_cpdefaults.py

 # Filters that are always included
 from cherrypy.lib.filter import baseurlfilter, cachefilter, \
     decodingfilter, encodingfilter, gzipfilter, logdebuginfofilter, \
-    sessionfilter, staticfilter, nsgmlsfilter, tidyfilter, \
+    staticfilter, nsgmlsfilter, tidyfilter, \
     virtualhostfilter, xmlrpcfilter
 
+from cherrypy.lib.filter.sessionfilter import sessionfilter
+
 _cachefilter = cachefilter.CacheFilter()
 _logdebuginfofilter = logdebuginfofilter.LogDebugInfoFilter()
 _nsgmlsfilter = nsgmlsfilter.NsgmlsFilter()

cherrypy/_cphttptools.py

     while chunk:
         yield chunk
         chunk = input.read(chunkSize)
+    input.close()
 
 def flattener(input):
     for x in input:

cherrypy/_cpserver.py

     #   throughout the entire life of the webserver)
     cpg.request = local()
     cpg.response = local()
+
+    # Create as sessions object for accessing session data
+    cpg.sessions = local()
     
     # Create threadData object as a thread-specific all-purpose storage
     cpg.threadData = local()

cherrypy/lib/filter/sessionfilter.py

-"""
-Copyright (c) 2005, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-from basefilter import BaseFilter
-import random, sha, time
-
-
-class SessionFilter(BaseFilter):
-    
-    def onStartResource(self):
-        # We have to dynamically import cpg because Python can't handle
-        #   circular module imports :-(
-        global cpg, _cputil
-        from cherrypy import cpg, _cputil
-        cpg.threadData.sessionFilterOn = False
-    
-    def beforeMain(self):
-        cpg.threadData.sessionFilterOn = bool(cpg.config.get('session.storageType'))
-        if cpg.threadData.sessionFilterOn:
-            cleanupSessionData()
-            if not cpg.request.isStatic:
-                getSessionData()
-    
-    def beforeFinalize(self):
-        if cpg.threadData.sessionFilterOn and not cpg.request.isStatic:
-            saveSessionData()
-    
-    def beforeErrorResponse(self):
-        # Still save session data
-        if cpg.threadData.sessionFilterOn and not cpg.request.isStatic:
-            saveSessionData()
-
-
-def getSessionData():
-    now = time.time()
-    cookieName = cpg.config.get('session.cookieName')
-    
-    # First, get sessionId from cookie
-    try:
-        sessionId = cpg.request.simpleCookie[cookieName].value
-    except:
-        sessionId = None
-    
-    if sessionId:
-        # Load session data from wherever it was stored
-        sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId)
-        if sessionData is None:
-            sessionId = None
-        else:
-            cpg.request.sessionMap, expirationTime = sessionData
-            if now > expirationTime:
-                # Session expired
-                sessionId = None
-    
-    # Create a new sessionId if needed
-    if not sessionId:
-        sessionId = generateSessionId()
-        cpg.request.sessionMap = {'_sessionId': sessionId}
-        
-        cpg.response.simpleCookie[cookieName] = sessionId
-        cpg.response.simpleCookie[cookieName]['path'] = '/'
-        cpg.response.simpleCookie[cookieName]['version'] = 1
-
-def generateSessionId():
-    s = "%s%s" % (random.random(), time.time())
-    return sha.sha(s).hexdigest()
-
-def cleanupSessionData():
-    """Clean up expired sessions if needed."""
-    now = time.time()
-    delay = cpg.config.get('session.cleanUpDelay')
-    if delay and (cpg._lastSessionCleanUpTime + (delay * 60) <= now):
-        cpg._lastSessionCleanUpTime = now
-        _cputil.getSpecialFunction('_cpCleanUpOldSessions')()
-
-def saveSessionData():
-    sessionId = cpg.request.sessionMap['_sessionId']
-    timeout = cpg.config.get('session.timeout')
-    expire = (time.time() + (timeout * 60))
-    savefunc = _cputil.getSpecialFunction('_cpSaveSessionData')
-    savefunc(sessionId, cpg.request.sessionMap, expire)

cherrypy/lib/filter/sessionfilter/__init__.py

+

cherrypy/lib/filter/sessionfilter/basesession.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import cherrypy.cpg
+
+import cherrypy._cputil, cherrypy.cperror
+import random, time, sha, string
+
+from sessiondict import SimpleSessionDict
+
+class BaseSession(object):
+    """
+    This is the class from which all session storage types are derived.
+    """
+
+    # configname is the string storageType value used by the 
+    # configuration file
+    configName = 'BaseSession'
+    
+    """
+    autoKeys is used to tell the server if the session storage class
+    can automaticly determine all of the key names.  This would be 
+    true if you are using a relational database and false if you are 
+    using python dictionaries.
+
+    If autoKeys is false the key names must be provided at runtime.
+    If it is true any key names provied at runtime are ignored.
+    """
+    autoKeys   = True
+    
+
+    def __init__(self, sessionName):
+        self.__sessionCache = {}
+        self.defaultValues = {}
+        self.sessionName = sessionName
+        """
+        This is where the you initialize your session storage class.
+        sessionOptions is a direct mapping of the configuration
+        options.
+        
+        The keys 'host', 'user', 'password', and 'database',
+        must be used by classes that need to connect to remote
+        databases.  'tableName' will contain the table name (duh!).
+
+        The keyw 'dataKeys' will map to a list of the variable names you wish
+        to store in your session object.  If autoKeys is true you will use it
+        to set to create sessionMap instances.
+        """
+    
+    def getDefaultAttributes(self):
+      return { 
+               'timestamp'  : int(time.time()),
+               'timeout'    : cherrypy.cpg.config.get('sessionFilter.timeout') * 60,
+               'lastAccess' : int(time.time()),
+               'key' : self.generateSessionId()
+             }
+       
+    def newSession(self):
+        """ Return a new sessionMap instance """
+        # this needs to check the config file for default values
+        newData = self.getDefaultAttributes()
+        newData.update(self.defaultValues)
+        return SimpleSessionDict(newData)
+
+    def generateSessionId(self):
+        """ Function to return a new sessioId """
+        cpg = cherrypy.cpg
+        sessionKeyFunc = cpg.config.get('%s.keyMaker' % self.sessionName, None)
+        
+        if sessionKeyFunc:
+            newKey = cherrypy._cputil.getSpecialFunction(sessionKeyFunc)()
+        else:
+            s = ''
+            for i in range(50):
+                s += random.choice(string.letters+string.digits)
+            s += '%s'%time.time()
+            newKey = sha.sha(s).hexdigest()
+        
+        return newKey
+
+    def loadSession(self, sessionKey, autoCreate = True):
+        cpg = cherrypy.cpg
+        
+        try:
+            # look for the session in the cache
+            session = self.__sessionCache[sessionKey]
+            session.threadCount += 1
+        except KeyError:
+            # look in the primary storage
+            session = self.getSession(sessionKey)
+            session.threadCount += 1
+            self.__sessionCache[sessionKey] = session
+        
+        if self.sessionName == 'sessionMap':
+            # raise a warning perhaps
+            setattr(cpg.request, self.sessionName, session)
+        setattr(cpg.sessions, self.sessionName, session)
+
+    def createSession(self):
+        """ returns a session key """
+        session = self.newSession()
+        self.setSession(session)
+        return session.key
+
+    def commitCache(self, sessionKey): 
+        cpg = cherrypy.cpg 
+        
+        session = self.__sessionCache[sessionKey]
+        session.threadCount = 0
+        self.setSession(session)
+        
+        cacheTimeout = cpg.config.get('sessionFilter.cacheTimeout', None)
+        
+        if session.threadCount == 0 and not cacheTimeout:
+            del self.__sessionCache[sessionKey]
+        """ commit a session to persistand storage """
+    
+    def cleanUpCache(self):
+        """ cleanup all inactive sessions """
+        
+        cacheTimeout = cpg.config.get('sessionFilter.cacheTimeout', None)
+        
+        # don't waste cycles if we aren't caching inactive sessions
+        if cacheTimeout:
+            for session in self.__sessionCache.itervalues():
+                # make sure the session doesn't have any active threads
+                expired = time.time() < (session.lastAccess + cacheTimeout)
+                if session.threadCount == 0 and expired:
+                    del self.__sessionCache[session.key]
+    
+    def dropSession(self, sessionKey):
+        self.delSession()
+        """ delete a session from storage """
+
+    def register(cls):
+        """
+        This method will place the configName and session object so
+        the configuration system will know that "BaseSession" maps to
+        the BaseSession class
+        """
+        cherrypy.cpg.session.registerSession(cls.configName, cls)
+    register=classmethod(register)
+
+    def cleanUpOldSessions(self):
+        """This function cleans up expired sessions"""
+

cherrypy/lib/filter/sessionfilter/dbmsession.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+from basesession import BaseSession
+import cherrypy.cpg
+
+import shelve
+
+from sessionerrors import *
+
+import cPickle as pickle
+
+class DBMSession(BaseSession):
+    def __init__(self, sessionName):
+        cpg = cherrypy.cpg
+
+        BaseSession.__init__(self, sessionName)
+        
+        sessionName=cpg.config.get('session.new', None)
+        sessionFile=cpg.config.get('%s.dbFile' % sessionName, 'shelfSession.db')
+        self.__data = shelve.open(sessionFile, 'c')
+
+    def getSession(self, sessionKey):
+        try:
+            return self.__data[sessionKey]
+        except KeyError:
+            raise SessionNotFoundError
+    
+    def setSession(self, sessionData):
+        self.__data[sessionData.key] = sessionData
+
+    def delSession(self, sessionKey):
+        try:
+            del self.__data[sessionKey]
+        except KeyError:
+            raise SessionNotFoundError
+    
+    def cleanUpOldSessions(self):
+        deleteList = []
+        for sessionKey in self.__data:
+            session = self.__data[sessionKey]
+            if session.expired():
+                deleteList.append(sessionKey)
+        for key in deleteList:
+            self.delSession(sessionKey)

cherrypy/lib/filter/sessionfilter/filesession.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+from basesession import BaseSession
+import cherrypy.cpg
+import cPickle as pickle
+
+import os.path
+
+from sessionerrors import *
+
+import threading
+
+class FileSession(BaseSession):
+  
+    def __init__(self, sessionName):
+        BaseSession.__init__(self, sessionName)
+        self.__fileLock = threading.RLock()
+
+    def __storageDir(self):
+        cpg = cherrypy.cpg
+        storageDir = cpg.config.get('%s.storageFileDir' % self.sessionName)
+        if not storageDir:
+            storageDir = cpg.config.get('sessionFilter.storageFileDir', '.sessionFiles')
+        return storageDir
+    
+    # all session writes are blocked 
+    def getSession(self, sessionKey):
+        sessionStorageFileDir = self.__storageDir()
+        print sessionStorageFileDir, sessionKey
+        fname = os.path.join(sessionStorageFileDir, sessionKey)
+        if os.path.exists(fname):
+            f = open(fname, "rb")
+            self.__fileLock.acquire()
+            sessionData = pickle.load(f)
+            self.__fileLock.release()
+            f.close()
+            return sessionData
+        else:
+            raise SessionNotFoundError
+    
+    def setSession(self, sessionData):
+        sessionStorageFileDir = self.__storageDir()
+    
+        fname=os.path.join(sessionStorageFileDir, sessionData.key)
+        f = open(fname,"wb")
+        self.__fileLock.acquire()
+        pickle.dump(sessionData, f)
+        self.__fileLock.acquire()
+        f.close()
+
+    def delSession(self, sessionKey):
+        sessionStorageFileDir = self.__storageDir()
+    
+    def cleanUpOldSessions(self):
+        sessionStorageFileDir = self.__storageDir()
+        sessionFileList = os.listdir(sessionStorageFileDir)
+
+        for sessionKey in sessionFileList:
+            session = self.getSession(sessionKey)
+            if session.expired():
+                try:
+                    os.remove(os.path.join(sessionStorageFileDir, sessionKey))
+                except:
+                    """ the session was probably removed already """

cherrypy/lib/filter/sessionfilter/ramsession.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+ERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+from basesession import BaseSession
+import cherrypy.cpg
+
+from sessionerrors import *
+
+class RamSession(BaseSession):
+    def __init__(self, sessionName):
+        BaseSession.__init__(self, sessionName)
+        self.__data = {}
+
+    def getSession(self, sessionKey):
+        try:
+            return self.__data[sessionKey]
+        except KeyError:
+            raise SessionNotFoundError
+    
+    def setSession(self, sessionData):
+        # since everything in in rame the 
+        # session we don't need to update the data
+        # unless int is a new session
+        if not self.__data.has_key(sessionData.key):
+            self.__data[sessionData.key] = sessionData
+
+    def delSession(self, sessionKey):
+        try:
+            del self.__data[sessionKey]
+        except KeyError:
+            raise SessionNotFoundError
+    
+    def cleanUpOldSessions(self):
+        deleteList = []
+        for sessionKey in self.__data:
+            session = self.__data[sessionKey]
+            if session.expired():
+                deleteList.append(sessionKey)
+        for key in deleteList:
+            self.delSession(sessionKey)

cherrypy/lib/filter/sessionfilter/sessiondict.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import time
+
+from sessionerrors import SessionImmutableError
+
+def locker(function):
+    def _inner(self, *args, **kwds):
+        self._lock.acquire()
+        result = function(self, *args, **kwds)
+        self._lock.release()
+        return result
+    return _inner
+
+import threading 
+
+from sessiondictbase import SessionDictBase
+
+class SimpleSessionDict(SessionDictBase):
+
+    def __init__(self, sessionData = {}):
+        self._lock = threading.RLock()
+        self.threadCount = 0
+        
+        self.__sessionData = sessionData.copy()
+        self.__sessionAttributes = {}
+        
+        # move the attributes into a seperate dictionary
+        for attr in ['timestamp', 'timeout', 'lastAccess', 'key']:
+            self.__sessionAttributes[attr] = self.__sessionData.pop(attr)
+
+    def get(self, key, default = None):
+        return self.__sessionData.get(key, default)
+    get=locker(get)
+    
+    def __getitem__(self, key):
+        return self.__sessionData[key]
+    __getitem__=locker(__getitem__)
+     
+    def __setitem__(self, key, value):
+        self.__sessionData[key] = value
+    __setitem__=locker(__setitem__)
+        
+    def __getattr__(self, attr):
+        try:
+          return self.__sessionAttributes[attr]
+        except KeyError:
+            return object.__getattribute__(self, attr)
+    __getattr__=locker(__getattr__)
+    
+    # this function we lock the hard way
+    # so we don't try to lock the lock
+    def __setattr__(self, attr, value):
+        if attr == '_lock':
+            object.__setattr__(self, attr, value)
+            return
+
+        self._lock.acquire()
+        if attr in ['key', 'timestamp']:
+            raise SessionImmutableError(attr)
+        elif attr in ['timeout', 'lastAccess']:
+            self.__sessionData[attr] = value
+        else:
+            object.__setattr__(self, attr, value)
+        self._lock.release()
+    
+    def __getstate__(self):
+        """ remove the lock so we can pickle """
+        stateDict = self.__dict__.copy()
+        stateDict['threadCount'] = 0
+        stateDict.pop('_lock')
+        return stateDict
+
+    def __setstate__(self, stateDict):
+        """ create a new lock object """
+        self.__dict__['_lock'] = threading.RLock()
+        self.__dict__.update(stateDict)
+

cherrypy/lib/filter/sessionfilter/sessiondictbase.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import cherrypy.cpg
+import time
+
+from sessionerrors import SessionImmutableError
+
+def locker(function):
+    def _inner(self, *args, **kwds):
+        self._lock.acquire()
+        result = function(self, *args, **kwds)
+        self._lock.release()
+        return result
+    return _inner
+
+import threading 
+
+# this is a dictionary like class that will be exposed to the application
+# this class is used by 
+class SessionDictBase(object):
+    """
+    cpg.request.sessionMap is a SessionDict instance.
+
+    SessionDict isntances alwasy contain the following attributes.
+    
+    'sessionKey' : A unique session identifier
+    
+    'timeout'    : The number of seconds of inactivity allowed before destroying the session
+
+    'timestamp'  : The time the session was created (seconds since the Epoch or time.time() )
+    'lastAccess' : The time the last session was accessed (seconds since the Epoch or time.time() )
+
+    sessionKey and createdAt are immutable and a SessionImmutableError will be raised if you
+    try to set one
+    """
+
+
+    def __init__(self):
+        pass
+    
+    def __eq__(self, sessionDict):
+        pass 
+        
+    def get(self, key, default = None):
+        pass 
+    def __getitem__(self, key):
+        pass 
+     
+    def __setitem__(self, key, value):
+        pass 
+
+    def __getattr__(self, attr):
+        pass 
+    
+    def __setattr__(self, attr, value):
+        pass 
+    
+    def expired(self):
+        now = time.time()
+        return (now - self.lastAccess) < self.timeout
+    
+    # additional functions
+    '''
+    def __getstate__(self):
+        pass
+
+    def __setstate__(self, stateDict):
+        pass
+    '''
+

cherrypy/lib/filter/sessionfilter/sessionerrors.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+class SessionError(Exception):
+    """ Base type for session exceptions. """
+    def __init__(self, *args, **kwds):
+        Exception.__init__(self, *args, **kwds)
+
+class SessionExpiredError(SessionError):
+    """ Possibly raised when the sessions Expire """
+
+class SessionNotFoundError(SessionError):
+    """ Possibly raised a browser sends a none with a session idwhen the sessions Expire """
+
+class SessionImmutableError(SessionError):
+    """ immutable exception """
+    def __init__(self, keyName):
+        self.keyName = keyName
+
+    def __str__(self):
+        return "%s is immutable" % self.keyName
+
+class SessionConfigError(SessionError):
+    """ finish later """
+
+class SessionBadStorageTypeError(SessionError):
+    def __init__(self, storageType):
+        self.storageType = self.storageType
+
+    def __str__(self):
+        return "Could not find %s storage type."
+    

cherrypy/lib/filter/sessionfilter/sessionfilter.py

+
+from cherrypy.lib.filter.basefilter import BaseFilter
+import cherrypy.cpg
+
+import os.path, time, re
+
+from sessionerrors import SessionNotFoundError 
+
+from ramsession  import RamSession 
+from filesession import FileSession
+from dbmsession  import DBMSession
+
+_sessionTypes = {
+                  'ram'       : RamSession,
+                  'file'      : FileSession,
+                  'anydb'     : DBMSession
+                }
+
+try:
+    # the user might not have sqlobject instaled
+    from sqlobjectsession  import SQLObjectSession
+    _sessionTypes['sqlobject']  = SQLObjectSession
+except ImportError:
+    pass
+    
+
+def _getSessions():
+    """ checks the config file for the sessions """
+    cpg = cherrypy.cpg
+    
+    sessions = {}
+    
+    path = cpg.config.get('sessionFilter.new', returnSection = True )
+    paths=os.path.split(path)
+    
+    sessionNames = cpg.config.getAll('sessionFilter.new')
+    for sessionPath in sessionNames:
+        sessionName = sessionNames[sessionPath]
+        sessionManager = cpg.config.get('%s.sessionManager' % sessionName, None)
+        if not sessionManager:
+            storageType = cpg.config.get('%s.storageType' % sessionName, 'ram')
+            
+            #sessionManager = _sessionTypes[storageType](sessionName)
+            try:
+                sessionManager = _sessionTypes[storageType](sessionName)
+            except KeyError:
+                storageType = cpg.config.get('%s.customStorageClass' % sessionName)
+                if storageType:
+                    try:
+                        cherrypy._cputil.getSpecialFunction(storageType)
+                    except cherrypy.cperror.InternalError:
+                        raise SessionBadStorageTypeError(storageType)
+                raise
+            
+            sessionManager.path = sessionPath
+            sessionManager.name = sessionName
+            sessionManager.lastCleanUp = time.time()
+            
+            cpg.config.update(
+                              {
+                                sessionPath : {'%s.sessionManager' % sessionName : sessionManager}
+                              }
+                             )
+        else: # try and clean up
+            cleanUpDelay = cpg.config.get('session.cleanUpDelay')
+            now = time.time()
+            lastCleanUp = sessionManager.lastCleanUp
+            if lastCleanUp + cleanUpDelay * 60 <= now:
+                sessionManager.cleanUpOldSessions()
+          
+        sessions[sessionName] = sessionManager
+    
+    return sessions
+
+class SessionFilter(BaseFilter):
+    """
+    Input filter - get the sessionId (or generate a new one) and load up the session data
+    """
+        
+    def __initSessions(self):
+        cpg = cherrypy.cpg
+        sessions = _getSessions()
+        sessionKeys = self.getSessionKeys()
+        
+        for sessionName in sessions:
+            sessionManager = sessions[sessionName]
+            sessionKey = sessionKeys.get(sessionName, None)
+            try:
+               sessionManager.loadSession(sessionKey)
+            except SessionNotFoundError:
+               newKey = sessionManager.createSession()
+               sessionManager.loadSession(newKey)
+               
+               self.setSessionKey(newKey, sessionManager.name) 
+                
+    def getSessionKeys(self):
+        """ 
+        Returns the all current sessionkeys as a dict
+        """
+        cpg = cherrypy.cpg
+        
+        sessionKeys= {}
+        
+        sessions = cpg.config.getAll('sessionFilter.new')
+        for sessionPath in sessions:
+            sessionName = sessions[sessionPath]
+            cookieName = cpg.config.get('%s.cookieName' % sessionName, None)
+            if not cookieName:
+                cookieName = cpg.config.get('session.cookieName') + '|' + re.sub('/','_', sessionPath) + '|' + sessionName
+                cpg.config.update({
+                                    sessionPath : {'%s.cookieName' % sessionName : cookieName}
+                                  })
+            try:
+                sessionKeys[sessionName] = cpg.request.simpleCookie[cookieName].value
+            except:
+                sessionKeys[sessionName] = None
+        return sessionKeys
+
+    def setSessionKey(self, sessionKey, sessionName):
+        """ 
+        Sets the session key in a cookie.  Aplications should not call this function,
+        but it might be usefull to redefine it.
+        """
+
+        cpg = cherrypy.cpg
+        
+        cookieName = cpg.config.get('%s.cookieName' % sessionName, None)
+
+        cpg.response.simpleCookie[cookieName] = sessionKey
+        cpg.response.simpleCookie[cookieName]['version'] = 1
+
+        path = cpg.config.get('%s.sessionManager' % sessionName, returnSection = True)
+        cpg.response.simpleCookie[cookieName]['path'] = path
+
+    
+    def __saveSessions(self):
+        cpg = cherrypy.cpg
+        sessions = _getSessions()
+        
+        for sessionName in sessions:
+            sessionManager = sessions[sessionName]
+            sessionData = getattr(cpg.sessions, sessionName)
+            sessionManager.commitCache(sessionData.key)
+    
+    def beforeMain(self):
+        cpg = cherrypy.cpg
+        if cpg.config.get('sessionFilter.on', False) and not cpg.request.isStatic:
+           self.__initSessions()
+
+    def beforeFinalize(self):
+        cpg = cherrypy.cpg
+        if cpg.config.get('sessionFilter.on', False) and not cpg.request.isStatic:
+            self.__saveSessions()
+
+    '''
+    #this breaks a test case
+    def beforeErrorResponse(self):
+        # Still save session data
+        if cpg.config.get('sessionFilter.on', False) and not cpg.request.isStatic:
+            self.__saveSessions()
+    '''

cherrypy/tutorial/tut10_sessionfilter.conf

+[/]
+server.socketPort = 8080
+server.threadPool = 10
+server.environment = "production"
+
+[/admin]
+sessionFilter.new='adminSession'
+adminSession.storageType='ram'
+
+[/forum]
+sessionFilter.new='forumSession'
+forumSession.storageType='ram'

cherrypy/tutorial/tut10_sessionfilter.py

+"""
+Tutorial 08 - Sessions
+
+Storing session data in CherryPy applications is very easy: cpg.request
+provides a dictionary called sessionMap that represents the session
+data for the current user. If you use RAM based sessions, you can store
+any kind of object into that dictionary; otherwise, you are limited to
+objects that can be pickled.
+"""
+
+from cherrypy import cpg
+
+class HitCounter:
+    def __examplePage(self, poweredBy, count, links, sessionKey):
+        yield '<html><head><title>sessionFilter exampe</title><body>\n'
+        yield 'This page uses %s based session storage.<br/>\n' % poweredBy
+        yield 'You have viewed this page %i times. <br/><br/>\n' % count
+        for link in links:
+            yield '<a href="/%s">%s</a>&nbsp;&nbsp;\n' % (link, link)
+
+        yield '<br/><br/>The Current SessionKey is: &nbsp;&nbsp;\n'
+        yield sessionKey
+        yield '\n</body></html>'
+
+    samplePages = ['admin', 'index', 'forum', 'sqlobject']
+
+    def admin(self):
+        adminCount = cpg.sessions.adminSession.get('adminCount', 0) + 1
+        cpg.sessions.adminSession['adminCount'] = adminCount
+        
+        key = cpg.sessions.adminSession.key
+        return self.__examplePage('ram', adminCount, self.samplePages, key)
+
+    admin.exposed = True
+    
+    def forum(self):
+        forumCount = cpg.sessions.forumSession.get('forumCount', 0) + 1
+        cpg.sessions.forumSession['forumCount'] = forumCount
+        
+        key = cpg.sessions.forumSession.key
+        return self.__examplePage('ram', forumCount, self.samplePages, key)
+    forum.exposed=True
+
+    def index(self):
+        # Increase the silly hit counter
+        count = cpg.sessions.sessionMap.get('count', 0) + 1
+
+        # Store the new value in the session dictionary
+        cpg.sessions.sessionMap['count'] = count
+
+        # And display a silly hit count message!
+        key = cpg.sessions.sessionMap.key
+        return self.__examplePage('ram', count, self.samplePages, key)
+
+    index.exposed = True
+
+cpg.root = HitCounter()
+
+cpg.config.update(file = 'tut10_sessionfilter.conf')
+cpg.server.start()