Changeset 1067
- Timestamp:
- 04/24/06 19:08:29
- Files:
-
- trunk/cherrypy/__init__.py (modified) (1 diff)
- trunk/cherrypy/lib/sessions.py (modified) (18 diffs)
- trunk/cherrypy/test/test_session_filter.py (modified) (2 diffs)
- trunk/cherrypy/tools.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/__init__.py
r1047 r1067 59 59 thread_data = local() 60 60 61 ### Create variables needed for session (see lib/sessionfilter.py for more info)62 ##from filters import sessionfilter63 ##session = sessionfilter.SessionWrapper()64 ##_session_data_holder = {} # Needed for RAM sessions only65 ##_session_lock_dict = {} # Needed for RAM sessions only66 ##_session_last_clean_up_time = datetime.datetime.now()67 68 61 def expose(func=None, alias=None): 69 62 """Expose the function, optionally providing an alias or set of aliases.""" trunk/cherrypy/lib/sessions.py
r1049 r1067 7 7 8 8 Global variables (RAM backend only): 9 - cherrypy._session_lock_dict: dictionary containing the locks for all session_id10 - cherrypy._session_data_holder: dictionary containing the data for all sessions9 - _session_lock_dict: dictionary containing the locks for all session_id 10 - _session_data_holder: dictionary containing the data for all sessions 11 11 12 12 """ … … 27 27 import cherrypy 28 28 29 _session_last_clean_up_time = datetime.datetime.now() 30 _session_data_holder = {} # Needed for RAM sessions only 31 _session_lock_dict = {} # Needed for RAM sessions only 32 29 33 30 34 def generate_id(): … … 32 36 return sha.new('%s' % random.random()).hexdigest() 33 37 38 39 def noop(data): pass 34 40 35 41 class Session: … … 44 50 """ 45 51 52 timeout = 60 53 locking = 'explicit' 54 deadlock_timeout = 30 55 clean_up_delay = 5 56 storage_type = 'Ram' 57 storage_class = None 58 cookie_name = 'session_id' 59 cookie_domain = None 60 cookie_secure = False 61 cookie_path = None 62 cookie_path_from_header = None 63 46 64 def __init__(self): 47 conf = cherrypy.config.get48 49 65 self.storage = None 50 66 self.locked = False 51 self.loaded = True 52 53 self.timeout = conf('session_filter.timeout', 60) 54 self.locking = conf('session_filter.locking', 'explicit') 55 self.on_create = conf('session_filter.on_create', lambda data: None) 56 self.on_renew = conf('session_filter.on_renew', lambda data: None) 57 self.on_delete = conf('session_filter.on_delete', lambda data: None) 58 gen_id = conf('session_filter.generate_id', generate_id) 59 self.deadlock_timeout = conf('session_filter.deadlock_timeout', 30) 60 61 clean_up_delay = conf('session_filter.clean_up_delay', 5) 62 clean_up_delay = datetime.timedelta(seconds = clean_up_delay * 60) 63 64 storage = conf('session_filter.storage_type', 'Ram').title() 67 self.loaded = False 68 self.saved = False 69 70 self.generate_id = generate_id 71 self.on_create = noop 72 self.on_renew = noop 73 self.on_delete = noop 74 75 def load(self): 76 clean_up_delay = datetime.timedelta(seconds = self.clean_up_delay * 60) 65 77 66 78 # People can set their own custom class 67 # through session_filter.storage_class 68 cls = conf('session_filter.storage_class', None) 69 if cls is None: 70 cls = globals()[storage + 'Storage'] 71 self.storage = cls() 79 # through tools.sessions.storage_class 80 if self.storage_class is None: 81 self.storage_class = globals()[self.storage_type.title() + 'Storage'] 82 self.storage = self.storage_class() 72 83 73 84 now = datetime.datetime.now() 74 85 # Check if we need to clean up old sessions 75 if cherrypy._session_last_clean_up_time + clean_up_delay < now: 76 cherrypy._session_last_clean_up_time = now 86 global _session_last_clean_up_time 87 if _session_last_clean_up_time + clean_up_delay < now: 88 _session_last_clean_up_time = now 77 89 # Run clean_up in other thread to avoid blocking this request 78 90 thread.start_new_thread(self.storage.clean_up, (self,)) … … 80 92 self.data = {} 81 93 94 if self.cookie_path is None: 95 if self.cookie_path_from_header is not None: 96 geth = cherrypy.request.headers.get 97 self.cookie_path = geth(self.cookie_path_from_header, None) 98 if self.cookie_path is None: 99 self.cookie_path = '/' 100 82 101 # Check if request came with a session ID 83 cookie_name = conf('session_filter.cookie_name', 'session_id') 84 cookie_domain = conf('session_filter.cookie_domain', None) 85 cookie_secure = conf('session_filter.cookie_secure', False) 86 cookie_path = conf('session_filter.cookie_path', None) 87 if cookie_path is None: 88 cookie_path_header = conf('session_filter.cookie_path_from_header', None) 89 if cookie_path_header is not None: 90 cookie_path = cherrypy.request.headers.get(cookie_path_header, None) 91 if cookie_path is None: 92 cookie_path = '/' 93 if cookie_name in cherrypy.request.simple_cookie: 102 if self.cookie_name in cherrypy.request.simple_cookie: 94 103 # It did: we mark the data as needing to be loaded 95 self.id = cherrypy.request.simple_cookie[ cookie_name].value104 self.id = cherrypy.request.simple_cookie[self.cookie_name].value 96 105 97 106 # If using implicit locking, acquire lock … … 101 110 else: 102 111 # No id yet 103 self.id = gen_id()112 self.id = self.generate_id() 104 113 self.data['_id'] = self.id 105 114 self.on_create(self.data) … … 107 116 # Set response cookie 108 117 cookie = cherrypy.response.simple_cookie 109 cookie[ cookie_name] = self.id110 cookie[ cookie_name]['path'] =cookie_path118 cookie[self.cookie_name] = self.id 119 cookie[self.cookie_name]['path'] = self.cookie_path 111 120 # We'd like to use the "max-age" param as 112 121 # http://www.faqs.org/rfcs/rfc2109.html indicates but IE doesn't … … 116 125 #cookie[cookie_name]['max-age'] = self.timeout * 60 117 126 gmt_expiration_time = time.gmtime(time.time() + (self.timeout * 60)) 118 cookie[ cookie_name]['expires'] = time.strftime(127 cookie[self.cookie_name]['expires'] = time.strftime( 119 128 "%a, %d-%b-%Y %H:%M:%S GMT", gmt_expiration_time) 120 if cookie_domain is not None:121 cookie[ cookie_name]['domain'] =cookie_domain122 if cookie_secure is True:123 cookie[ cookie_name]['secure'] = 1129 if self.cookie_domain is not None: 130 cookie[self.cookie_name]['domain'] = self.cookie_domain 131 if self.cookie_secure is True: 132 cookie[self.cookie_name]['secure'] = 1 124 133 125 134 def save(self): … … 135 144 # Always release the lock if the user didn't release it 136 145 self.storage.release_lock() 146 147 self.saved = True 137 148 138 149 … … 143 154 144 155 class SessionNotEnabledError(Exception): 145 """User forgot to set session_filter.on to True"""156 """User forgot to set tools.sessions.on to True""" 146 157 pass 147 158 148 class SessionStoragePath NotConfiguredError(Exception):159 class SessionStoragePathError(Exception): 149 160 """User set storage_type to file but forgot to set the storage_path""" 150 161 pass 151 162 152 163 153 class SessionFilter(basefilter.BaseFilter):154 155 def before_request_body(self):156 if cherrypy.config.get('session_filter.on', False):157 cherrypy.request._session = Session()158 159 def before_finalize(self):160 sess = getattr(cherrypy.request, "_session", None)161 if sess:162 # Make a wrapper around the body in order to save the session163 # either before or after the body is returned164 def save_session_data(body, sess):165 if isinstance(body, types.GeneratorType):166 for line in body:167 yield line168 sess.save()169 else:170 sess.save()171 for line in body:172 yield line173 cherrypy.response.body = save_session_data(cherrypy.response.body, sess)174 175 def on_end_request(self):176 sess = getattr(cherrypy.request, "_session", None)177 if sess:178 if sess.locked:179 # If the session is still locked we release the lock180 sess.storage.release_lock()181 if sess.storage:182 sess.storage = None183 184 185 164 class RamStorage: 186 165 """ Implementation of the RAM backend for sessions """ 187 166 188 167 def load(self, id): 189 return cherrypy._session_data_holder.get(id)168 return _session_data_holder.get(id) 190 169 191 170 def save(self, id, data, expiration_time): 192 cherrypy._session_data_holder[id] = (data, expiration_time)171 _session_data_holder[id] = (data, expiration_time) 193 172 194 173 def acquire_lock(self): 195 174 sess = cherrypy.request._session 196 175 id = cherrypy.session.id 197 lock = cherrypy._session_lock_dict.get(id)176 lock = _session_lock_dict.get(id) 198 177 if lock is None: 199 178 lock = threading.Lock() 200 cherrypy._session_lock_dict[id] = lock179 _session_lock_dict[id] = lock 201 180 startTime = time.time() 202 181 while True: … … 209 188 210 189 def release_lock(self): 211 sess = cherrypy.request._session 212 id = cherrypy.session['_id'] 213 cherrypy._session_lock_dict[id].release() 214 sess.locked = False 190 _session_lock_dict[cherrypy.session['_id']].release() 191 cherrypy.request._session.locked = False 215 192 216 193 def clean_up(self, sess): 217 194 to_be_deleted = [] 218 195 now = datetime.datetime.now() 219 for id, (data, expiration_time) in cherrypy._session_data_holder.iteritems():196 for id, (data, expiration_time) in _session_data_holder.iteritems(): 220 197 if expiration_time < now: 221 198 to_be_deleted.append(id) 222 199 for id in to_be_deleted: 223 200 try: 224 deleted_session = cherrypy._session_data_holder[id]225 del cherrypy._session_data_holder[id]201 deleted_session = _session_data_holder[id] 202 del _session_data_holder[id] 226 203 sess.on_delete(deleted_session) 227 204 except KeyError: … … 254 231 255 232 def acquire_lock(self): 256 sess = cherrypy.request._session257 233 file_path = self._get_file_path(cherrypy.session.id) 258 lock_file_path = file_path + self.LOCK_SUFFIX 259 self._lock_file(lock_file_path) 260 sess.locked = True 234 self._lock_file(file_path + self.LOCK_SUFFIX) 235 cherrypy.request._session.locked = True 261 236 262 237 def release_lock(self): 263 sess = cherrypy.request._session264 238 file_path = self._get_file_path(cherrypy.session.id) 265 lock_file_path = file_path + self.LOCK_SUFFIX 266 self._unlock_file(lock_file_path) 267 sess.locked = False 239 self._unlock_file(file_path + self.LOCK_SUFFIX) 240 cherrypy.request._session.locked = False 268 241 269 242 def clean_up(self, sess): 270 storage_path = cherrypy.config.get('session_filter.storage_path')243 storage_path = getattr(sess, "storage_path") 271 244 if storage_path is None: 272 245 return … … 294 267 295 268 def _get_file_path(self, id): 296 storage_path = cherrypy.config.get('session_filter.storage_path')269 storage_path = getattr(cherrypy.request._session, "storage_path") 297 270 if storage_path is None: 298 raise SessionStoragePath NotConfiguredError()271 raise SessionStoragePathError() 299 272 fileName = self.SESSION_PREFIX + id 300 273 file_path = os.path.join(storage_path, fileName) … … 302 275 303 276 def _lock_file(self, path): 304 sess = cherrypy.request._session277 timeout = cherrypy.request._session.deadlock_timeout 305 278 startTime = time.time() 306 279 while True: … … 308 281 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL) 309 282 except OSError: 310 if time.time() - startTime > sess.deadlock_timeout:283 if time.time() - startTime > timeout: 311 284 raise SessionDeadlockError() 312 285 time.sleep(0.5) … … 331 304 332 305 def __init__(self): 333 self.db = cherrypy. config.get('session_filter.get_db')()306 self.db = cherrypy.request._session.get_db() 334 307 self.cursor = self.db.cursor() 335 308 … … 407 380 elif name == 'id': 408 381 return sess.id 409 382 410 383 if not sess.loaded: 411 384 data = sess.storage.load(sess.id) … … 423 396 return getattr(sess.data, name) 424 397 398 399 # The actual hook functions 400 401 def save(): 402 # Save the session either before or after the body is returned 403 if not isinstance(cherrypy.response.body, types.GeneratorType): 404 cherrypy.request._session.save() 405 406 def cleanup(): 407 sess = cherrypy.request._session 408 if not sess.saved: 409 sess.save() 410 411 if sess.locked: 412 # If the session is still locked we release the lock 413 sess.storage.release_lock() 414 if sess.storage: 415 sess.storage = None 416 417 def wrap(*args, **kwargs): 418 """Make a decorator for this tool.""" 419 def deco(f): 420 def wrapper(*a, **kw): 421 return f(*a, **kw) 422 save(*args, **kwargs) 423 cherrypy.request.hooks.attach('on_end_request', cleanup) 424 return wrapper 425 return deco 426 427 def setup(conf): 428 """Hook this tool into cherrypy.request using the given conf. 429 430 The standard CherryPy request object will automatically call this 431 method when the tool is "turned on" in config. 432 """ 433 def wrapper(): 434 s = cherrypy.request._session = Session() 435 for k, v in conf.iteritems(): 436 setattr(s, str(k), v) 437 s.load() 438 439 if not hasattr(cherrypy, "session"): 440 cherrypy.session = SessionWrapper() 441 442 cherrypy.request.hooks.attach('before_request_body', wrapper) 443 cherrypy.request.hooks.attach('before_finalize', save) 444 cherrypy.request.hooks.attach('on_end_request', cleanup) trunk/cherrypy/test/test_session_filter.py
r1017 r1067 20 20 21 21 def setsessiontype(self, newtype): 22 cherrypy.config.update({' session_filter.storage_type': newtype})22 cherrypy.config.update({'tools.sessions.storage_type': newtype}) 23 23 setsessiontype.exposed = True 24 24 … … 27 27 'server.log_to_screen': False, 28 28 'server.environment': 'production', 29 ' session_filter.on': True,30 ' session_filter.storage_type' : 'file',31 ' session_filter.storage_path' : '.',29 'tools.sessions.on': True, 30 'tools.sessions.storage_type' : 'file', 31 'tools.sessions.storage_path' : '.', 32 32 }) 33 33 trunk/cherrypy/tools.py
r1060 r1067 126 126 127 127 from cherrypy.lib import cptools 128 session_auth = MainTool(cptools.session_auth) 128 129 base_url = Tool('before_request_body', cptools.base_url) 129 130 response_headers = Tool('before_finalize', cptools.response_headers) … … 153 154 154 155 # These modules are themselves Tools 155 from cherrypy.lib import caching, xmlrpc156 from cherrypy.lib import caching, sessions, xmlrpc

