1.1 --- a/README.txt Sun Sep 05 11:12:32 2004 +0000
1.2 +++ b/README.txt Sun Sep 05 15:51:44 2004 +0000
1.3 @@ -41,6 +41,7 @@
1.4 Fixed field/parameter retrieval so that path and body fields are distinct,
1.5 regardless of the framework employed.
1.6 Introduced Zope 2 support.
1.7 +Session support has been added.
1.8
1.9 New in WebStack 0.6 (Changes Since WebStack 0.5)
1.10 ------------------------------------------------
1.11 @@ -96,6 +97,9 @@
1.12 This should be reviewed at a later date when proper standardisation has taken
1.13 place.
1.14
1.15 +Session support, especially through WebStack.Helpers.Session, should be
1.16 +reviewed.
1.17 +
1.18 Release Procedures
1.19 ------------------
1.20
2.1 --- a/WebStack/BaseHTTPRequestHandler.py Sun Sep 05 11:12:32 2004 +0000
2.2 +++ b/WebStack/BaseHTTPRequestHandler.py Sun Sep 05 15:51:44 2004 +0000
2.3 @@ -8,6 +8,7 @@
2.4 from Helpers.Request import MessageBodyStream, get_body_fields, get_storage_items
2.5 from Helpers.Response import ConvertingStream
2.6 from Helpers.Auth import UserInfo
2.7 +from Helpers.Session import SessionStore
2.8 from cgi import parse_qs, FieldStorage
2.9 import Cookie
2.10 from StringIO import StringIO
2.11 @@ -44,6 +45,10 @@
2.12
2.13 self.storage_body = None
2.14
2.15 + # Special objects retained throughout the transaction.
2.16 +
2.17 + self.session_store = None
2.18 +
2.19 def commit(self):
2.20
2.21 """
2.22 @@ -51,6 +56,13 @@
2.23 objects.
2.24 """
2.25
2.26 + # Close the session store.
2.27 +
2.28 + if self.session_store is not None:
2.29 + self.session_store.close()
2.30 +
2.31 + # Prepare the response.
2.32 +
2.33 self.trans.send_response(self.response_code)
2.34 if self.content_type is not None:
2.35 self.trans.send_header("Content-Type", str(self.content_type))
2.36 @@ -370,6 +382,42 @@
2.37 self.cookies_out[cookie_name]["expires"] = 0
2.38 self.cookies_out[cookie_name]["max-age"] = 0
2.39
2.40 + # Session-related methods.
2.41 +
2.42 + def get_session(self, create=1):
2.43 +
2.44 + """
2.45 + Gets a session corresponding to an identifier supplied in the
2.46 + transaction.
2.47 +
2.48 + If no session has yet been established according to information
2.49 + provided in the transaction then the optional 'create' parameter
2.50 + determines whether a new session will be established.
2.51 +
2.52 + Where no session has been established and where 'create' is set to 0
2.53 + then None is returned. In all other cases, a session object is created
2.54 + (where appropriate) and returned.
2.55 + """
2.56 +
2.57 + # NOTE: Requires configuration.
2.58 +
2.59 + if self.session_store is None:
2.60 + self.session_store = SessionStore(self, "WebStack-sessions")
2.61 + return self.session_store.get_session(create)
2.62 +
2.63 + def expire_session(self):
2.64 +
2.65 + """
2.66 + Expires any session established according to information provided in the
2.67 + transaction.
2.68 + """
2.69 +
2.70 + # NOTE: Requires configuration.
2.71 +
2.72 + if self.session_store is None:
2.73 + self.session_store = SessionStore(self, "WebStack-sessions")
2.74 + self.session_store.expire_session()
2.75 +
2.76 # Application-specific methods.
2.77
2.78 def set_user(self, username):
3.1 --- a/WebStack/CGI.py Sun Sep 05 11:12:32 2004 +0000
3.2 +++ b/WebStack/CGI.py Sun Sep 05 15:51:44 2004 +0000
3.3 @@ -9,6 +9,7 @@
3.4 from Helpers.Request import MessageBodyStream, get_body_fields, get_storage_items
3.5 from Helpers.Response import ConvertingStream
3.6 from Helpers.Auth import UserInfo
3.7 +from Helpers.Session import SessionStore
3.8 from Helpers import Environment
3.9 from cgi import parse_qs, FieldStorage
3.10 import Cookie
3.11 @@ -49,6 +50,10 @@
3.12
3.13 self.storage_body = None
3.14
3.15 + # Special objects retained throughout the transaction.
3.16 +
3.17 + self.session_store = None
3.18 +
3.19 def commit(self):
3.20
3.21 """
3.22 @@ -58,6 +63,11 @@
3.23 See draft-coar-cgi-v11-03, section 7.
3.24 """
3.25
3.26 + # Close the session store.
3.27 +
3.28 + if self.session_store is not None:
3.29 + self.session_store.close()
3.30 +
3.31 # NOTE: Provide sensible messages.
3.32
3.33 self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status"))
3.34 @@ -359,6 +369,42 @@
3.35 self.cookies_out[cookie_name]["expires"] = 0
3.36 self.cookies_out[cookie_name]["max-age"] = 0
3.37
3.38 + # Session-related methods.
3.39 +
3.40 + def get_session(self, create=1):
3.41 +
3.42 + """
3.43 + Gets a session corresponding to an identifier supplied in the
3.44 + transaction.
3.45 +
3.46 + If no session has yet been established according to information
3.47 + provided in the transaction then the optional 'create' parameter
3.48 + determines whether a new session will be established.
3.49 +
3.50 + Where no session has been established and where 'create' is set to 0
3.51 + then None is returned. In all other cases, a session object is created
3.52 + (where appropriate) and returned.
3.53 + """
3.54 +
3.55 + # NOTE: Requires configuration.
3.56 +
3.57 + if self.session_store is None:
3.58 + self.session_store = SessionStore(self, "WebStack-sessions")
3.59 + return self.session_store.get_session(create)
3.60 +
3.61 + def expire_session(self):
3.62 +
3.63 + """
3.64 + Expires any session established according to information provided in the
3.65 + transaction.
3.66 + """
3.67 +
3.68 + # NOTE: Requires configuration.
3.69 +
3.70 + if self.session_store is None:
3.71 + self.session_store = SessionStore(self, "WebStack-sessions")
3.72 + self.session_store.expire_session()
3.73 +
3.74 # Application-specific methods.
3.75
3.76 def set_user(self, username):
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/WebStack/Helpers/Session.py Sun Sep 05 15:51:44 2004 +0000
4.3 @@ -0,0 +1,188 @@
4.4 +#!/usr/bin/env python
4.5 +
4.6 +"""
4.7 +Session helper functions.
4.8 +"""
4.9 +
4.10 +import shelve
4.11 +import os
4.12 +import glob
4.13 +import time
4.14 +import random
4.15 +import sys
4.16 +
4.17 +class SessionStore:
4.18 +
4.19 + "A class representing a session store."
4.20 +
4.21 + def __init__(self, trans, session_directory, session_cookie_name="SID", concurrent=1, delay=1):
4.22 +
4.23 + """
4.24 + Initialise the session store, specifying the transaction 'trans' within
4.25 + which all session access will occur, a base 'session_directory', the
4.26 + optional 'session_cookie_name' where the session identifier is held for
4.27 + each user, and specifying using the optional 'concurrent' parameter
4.28 + whether concurrent access within the framework might occur (1) or
4.29 + whether the framework queues accesses at some other level (0). The
4.30 + optional 'delay' argument specifies the time in seconds between each
4.31 + poll of the session file when that file is found to be locked for
4.32 + editing.
4.33 + """
4.34 +
4.35 + self.trans = trans
4.36 + self.session_directory = session_directory
4.37 + self.session_cookie_name = session_cookie_name
4.38 + self.concurrent = concurrent
4.39 + self.delay = delay
4.40 +
4.41 + # Internal state.
4.42 +
4.43 + self.store = None
4.44 + self.store_filename, self.edit_filename = None, None
4.45 + self.to_expire = None
4.46 +
4.47 + def close(self):
4.48 +
4.49 + "Close the store, tidying up files and filenames."
4.50 +
4.51 + if self.store is not None:
4.52 + self.store.close()
4.53 + self.store = None
4.54 + if self.edit_filename is not None:
4.55 + try:
4.56 + os.rename(self.edit_filename, self.store_filename)
4.57 + except OSError:
4.58 + pass
4.59 + self.edit_filename, self.store_filename = None, None
4.60 +
4.61 + # Handle expiry appropriately.
4.62 +
4.63 + if self.to_expire is not None:
4.64 + self._expire_session(self.to_expire)
4.65 + self.trans.delete_cookie(self.session_cookie_name)
4.66 +
4.67 + def expire_session(self):
4.68 +
4.69 + """
4.70 + Expire the session in the given transaction.
4.71 + """
4.72 +
4.73 + # Perform expiry.
4.74 +
4.75 + cookie = self.trans.get_cookie(self.session_cookie_name)
4.76 + if cookie:
4.77 + self.to_expire = cookie.value
4.78 +
4.79 + def _expire_session(self, session_id):
4.80 +
4.81 + """
4.82 + Expire the session with the given 'session_id'. Note that in concurrent
4.83 + session stores, this operation will block if another execution context
4.84 + is editing the session.
4.85 + """
4.86 +
4.87 + filename = os.path.join(self.session_directory, session_id)
4.88 + if self.concurrent:
4.89 + while 1:
4.90 + try:
4.91 + os.unlink(filename)
4.92 + except OSError:
4.93 + time.sleep(self.delay)
4.94 + else:
4.95 + break
4.96 + else:
4.97 + try:
4.98 + os.unlink(filename)
4.99 + except OSError:
4.100 + pass
4.101 +
4.102 + def get_session(self, create):
4.103 +
4.104 + """
4.105 + Get the session for the given transaction, creating a new session if
4.106 + 'create' is set to 1 (rather than 0). Where new sessions are created, an
4.107 + appropriate session identifier cookie will be created.
4.108 + Returns a session object or None if no session exists and none is then
4.109 + created.
4.110 + """
4.111 +
4.112 + cookie = self.trans.get_cookie(self.session_cookie_name)
4.113 + if cookie:
4.114 + return self._get_session(cookie.value, create)
4.115 + elif create:
4.116 + session_id = self._get_session_identifier()
4.117 + self.trans.set_cookie_value(self.session_cookie_name, session_id)
4.118 + return self._get_session(session_id, create)
4.119 + else:
4.120 + return None
4.121 +
4.122 + def _get_session(self, session_id, create):
4.123 +
4.124 + """
4.125 + Get a session with the given 'session_id' and whether new sessions
4.126 + should be created ('create' set to 1).
4.127 + Returns a dictionary-like object representing the session.
4.128 + """
4.129 +
4.130 + filename = os.path.join(self.session_directory, session_id)
4.131 +
4.132 + # Enforce locking.
4.133 +
4.134 + if self.concurrent:
4.135 +
4.136 + # Where the session is present (possibly being edited)...
4.137 +
4.138 + if glob.glob(filename + "*"):
4.139 + while 1:
4.140 + try:
4.141 + os.rename(filename, filename + ".edit")
4.142 + except OSError:
4.143 + time.sleep(self.delay)
4.144 + else:
4.145 + break
4.146 +
4.147 + # Where no session is present and none should be created, return.
4.148 +
4.149 + elif not create:
4.150 + return None
4.151 +
4.152 + self.store_filename = filename
4.153 + filename = filename + ".edit"
4.154 + self.edit_filename = filename
4.155 +
4.156 + # For non-concurrent situations, return if no session exists and none
4.157 + # should be created.
4.158 +
4.159 + elif not os.path.exists(filename) and not create:
4.160 + return None
4.161 +
4.162 + self.store = shelve.open(filename)
4.163 + return Wrapper(self.store)
4.164 +
4.165 + def _get_session_identifier(self):
4.166 +
4.167 + "Return a session identifier as a string."
4.168 +
4.169 + g = random.Random()
4.170 + return str(g.randint(0, sys.maxint - 1))
4.171 +
4.172 +class Wrapper:
4.173 +
4.174 + "A wrapper around shelf objects."
4.175 +
4.176 + def __init__(self, store):
4.177 + self.store = store
4.178 +
4.179 + def __getattr__(self, name):
4.180 + if hasattr(self.store, name):
4.181 + return getattr(self.store, name)
4.182 + else:
4.183 + raise AttributeError, name
4.184 +
4.185 + def items(self):
4.186 + l = []
4.187 + for key in self.store.keys():
4.188 + l.append((key, self.store[key]))
4.189 + return l
4.190 +
4.191 +# vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/WebStack/ModPython.py Sun Sep 05 11:12:32 2004 +0000
5.2 +++ b/WebStack/ModPython.py Sun Sep 05 15:51:44 2004 +0000
5.3 @@ -10,12 +10,17 @@
5.4 from mod_python.util import parse_qs, FieldStorage
5.5 from mod_python import apache
5.6
5.7 -# NOTE: Should provide alternative implementations.
5.8 +# NOTE: Provide an alternative implementation for the cookie support.
5.9 +# NOTE: The alternative session support requires cookie support.
5.10
5.11 try: from mod_python import Cookie
5.12 except ImportError: Cookie = None
5.13 -try: from mod_python import Session
5.14 -except ImportError: Session = None
5.15 +try:
5.16 + from mod_python import Session
5.17 +except ImportError:
5.18 + from Helpers.Session import SessionStore
5.19 + import os
5.20 + Session = None
5.21
5.22 class Transaction(Generic.Transaction):
5.23
5.24 @@ -36,6 +41,22 @@
5.25
5.26 self.storage_body = None
5.27
5.28 + # Special objects retained throughout the transaction.
5.29 +
5.30 + self.session_store = None
5.31 +
5.32 + def commit(self):
5.33 +
5.34 + """
5.35 + A special method, synchronising the transaction with framework-specific
5.36 + objects.
5.37 + """
5.38 +
5.39 + # Close the session store.
5.40 +
5.41 + if self.session_store is not None:
5.42 + self.session_store.close()
5.43 +
5.44 # Request-related methods.
5.45
5.46 def get_request_stream(self):
5.47 @@ -356,7 +377,11 @@
5.48 # NOTE: Not exposing all functionality.
5.49 return Session.Session(self.trans)
5.50 else:
5.51 - return None
5.52 + # NOTE: Requires configuration.
5.53 +
5.54 + if self.session_store is None:
5.55 + self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions"))
5.56 + return self.session_store.get_session(create)
5.57
5.58 def expire_session(self):
5.59
5.60 @@ -365,9 +390,16 @@
5.61 transaction.
5.62 """
5.63
5.64 - session = self.get_session(create=0)
5.65 - if session:
5.66 - session.invalidate()
5.67 + if Session:
5.68 + session = self.get_session(create=0)
5.69 + if session:
5.70 + session.invalidate()
5.71 + else:
5.72 + # NOTE: Requires configuration.
5.73 +
5.74 + if self.session_store is None:
5.75 + self.session_store = SessionStore(self, os.path.join(apache.server_root(), "WebStack-sessions"))
5.76 + self.session_store.expire_session()
5.77
5.78 # Application-specific methods.
5.79
6.1 --- a/WebStack/Twisted.py Sun Sep 05 11:12:32 2004 +0000
6.2 +++ b/WebStack/Twisted.py Sun Sep 05 15:51:44 2004 +0000
6.3 @@ -8,6 +8,7 @@
6.4 from Helpers.Auth import UserInfo
6.5 from Helpers.Request import Cookie, get_body_field
6.6 from Helpers.Response import ConvertingStream
6.7 +from Helpers.Session import SessionStore
6.8 from cgi import parse_qs
6.9
6.10 class Transaction(Generic.Transaction):
6.11 @@ -24,6 +25,22 @@
6.12 self.user = None
6.13 self.content_type = None
6.14
6.15 + # Special objects retained throughout the transaction.
6.16 +
6.17 + self.session_store = None
6.18 +
6.19 + def commit(self):
6.20 +
6.21 + """
6.22 + A special method, synchronising the transaction with framework-specific
6.23 + objects.
6.24 + """
6.25 +
6.26 + # Close the session store.
6.27 +
6.28 + if self.session_store is not None:
6.29 + self.session_store.close()
6.30 +
6.31 # Request-related methods.
6.32
6.33 def get_request_stream(self):
6.34 @@ -341,6 +358,42 @@
6.35
6.36 self.trans.addCookie(cookie_name, "", expires=0, path="/", max_age=0)
6.37
6.38 + # Session-related methods.
6.39 +
6.40 + def get_session(self, create=1):
6.41 +
6.42 + """
6.43 + Gets a session corresponding to an identifier supplied in the
6.44 + transaction.
6.45 +
6.46 + If no session has yet been established according to information
6.47 + provided in the transaction then the optional 'create' parameter
6.48 + determines whether a new session will be established.
6.49 +
6.50 + Where no session has been established and where 'create' is set to 0
6.51 + then None is returned. In all other cases, a session object is created
6.52 + (where appropriate) and returned.
6.53 + """
6.54 +
6.55 + # NOTE: Requires configuration.
6.56 +
6.57 + if self.session_store is None:
6.58 + self.session_store = SessionStore(self, "WebStack-sessions")
6.59 + return self.session_store.get_session(create)
6.60 +
6.61 + def expire_session(self):
6.62 +
6.63 + """
6.64 + Expires any session established according to information provided in the
6.65 + transaction.
6.66 + """
6.67 +
6.68 + # NOTE: Requires configuration.
6.69 +
6.70 + if self.session_store is None:
6.71 + self.session_store = SessionStore(self, "WebStack-sessions")
6.72 + self.session_store.expire_session()
6.73 +
6.74 # Application-specific methods.
6.75
6.76 def set_user(self, username):
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/docs/SESSION.txt Sun Sep 05 15:51:44 2004 +0000
7.3 @@ -0,0 +1,19 @@
7.4 +Session Support in WebStack
7.5 +---------------------------
7.6 +
7.7 +Various frameworks do not support sessions. In order to provide primitive
7.8 +support for sessions within WebStack upon such frameworks, the
7.9 +WebStack.Helpers.Session module is used to provide a simple file-based session
7.10 +store. It is necessary to create a directory called WebStack-sessions in a
7.11 +particular location for the session store to function, and the location depends
7.12 +on the framework as summarised in the following table.
7.13 +
7.14 +Framework Location
7.15 +--------- --------
7.16 +BaseHTTPRequestHandler The directory where the server is run.
7.17 +CGI The directory where the handler resides.
7.18 +mod_python The server root (eg. /usr/local/apache2).
7.19 +
7.20 +Note that the WebStack-sessions directory must have the appropriate ownership
7.21 +and privileges necessary for the server/framework to write session files into
7.22 +it.