Package cherrypy :: Package lib :: Module sessions
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.sessions

  1  """Session implementation for CherryPy. 
  2   
  3  You need to edit your config file to use sessions. Here's an example:: 
  4   
  5      [/] 
  6      tools.sessions.on = True 
  7      tools.sessions.storage_type = "file" 
  8      tools.sessions.storage_path = "/home/site/sessions" 
  9      tools.sessions.timeout = 60 
 10   
 11  This sets the session to be stored in files in the directory 
 12  /home/site/sessions, and the session timeout to 60 minutes. If you omit 
 13  ``storage_type`` the sessions will be saved in RAM. 
 14  ``tools.sessions.on`` is the only required line for working sessions, 
 15  the rest are optional. 
 16   
 17  By default, the session ID is passed in a cookie, so the client's browser must 
 18  have cookies enabled for your site. 
 19   
 20  To set data for the current session, use 
 21  ``cherrypy.session['fieldname'] = 'fieldvalue'``; 
 22  to get data use ``cherrypy.session.get('fieldname')``. 
 23   
 24  ================ 
 25  Locking sessions 
 26  ================ 
 27   
 28  By default, the ``'locking'`` mode of sessions is ``'implicit'``, which means 
 29  the session is locked early and unlocked late. Be mindful of this default mode 
 30  for any requests that take a long time to process (streaming responses, 
 31  expensive calculations, database lookups, API calls, etc), as other concurrent 
 32  requests that also utilize sessions will hang until the session is unlocked. 
 33   
 34  If you want to control when the session data is locked and unlocked, 
 35  set ``tools.sessions.locking = 'explicit'``. Then call 
 36  ``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``. 
 37  Regardless of which mode you use, the session is guaranteed to be unlocked when 
 38  the request is complete. 
 39   
 40  ================= 
 41  Expiring Sessions 
 42  ================= 
 43   
 44  You can force a session to expire with :func:`cherrypy.lib.sessions.expire`. 
 45  Simply call that function at the point you want the session to expire, and it 
 46  will cause the session cookie to expire client-side. 
 47   
 48  =========================== 
 49  Session Fixation Protection 
 50  =========================== 
 51   
 52  If CherryPy receives, via a request cookie, a session id that it does not 
 53  recognize, it will reject that id and create a new one to return in the 
 54  response cookie. This `helps prevent session fixation attacks 
 55  <http://en.wikipedia.org/wiki/Session_fixation#Regenerate_SID_on_each_request>`_. 
 56  However, CherryPy "recognizes" a session id by looking up the saved session 
 57  data for that id. Therefore, if you never save any session data, 
 58  **you will get a new session id for every request**. 
 59   
 60  ================ 
 61  Sharing Sessions 
 62  ================ 
 63   
 64  If you run multiple instances of CherryPy (for example via mod_python behind 
 65  Apache prefork), you most likely cannot use the RAM session backend, since each 
 66  instance of CherryPy will have its own memory space. Use a different backend 
 67  instead, and verify that all instances are pointing at the same file or db 
 68  location. Alternately, you might try a load balancer which makes sessions 
 69  "sticky". Google is your friend, there. 
 70   
 71  ================ 
 72  Expiration Dates 
 73  ================ 
 74   
 75  The response cookie will possess an expiration date to inform the client at 
 76  which point to stop sending the cookie back in requests. If the server time 
 77  and client time differ, expect sessions to be unreliable. **Make sure the 
 78  system time of your server is accurate**. 
 79   
 80  CherryPy defaults to a 60-minute session timeout, which also applies to the 
 81  cookie which is sent to the client. Unfortunately, some versions of Safari 
 82  ("4 public beta" on Windows XP at least) appear to have a bug in their parsing 
 83  of the GMT expiration date--they appear to interpret the date as one hour in 
 84  the past. Sixty minutes minus one hour is pretty close to zero, so you may 
 85  experience this bug as a new session id for every request, unless the requests 
 86  are less than one second apart. To fix, try increasing the session.timeout. 
 87   
 88  On the other extreme, some users report Firefox sending cookies after their 
 89  expiration date, although this was on a system with an inaccurate system time. 
 90  Maybe FF doesn't trust system time. 
 91  """ 
 92  import sys 
 93  import datetime 
 94  import os 
 95  import time 
 96  import threading 
 97  import types 
 98   
 99  import cherrypy 
100  from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr 
101  from cherrypy.lib import httputil 
102  from cherrypy.lib import lockfile 
103  from cherrypy.lib import locking 
104  from cherrypy.lib import is_iterator 
105   
106  missing = object() 
107   
108   
109 -class Session(object):
110 111 """A CherryPy dict-like Session object (one per request).""" 112 113 _id = None 114 115 id_observers = None 116 "A list of callbacks to which to pass new id's." 117
118 - def _get_id(self):
119 return self._id
120
121 - def _set_id(self, value):
122 self._id = value 123 for o in self.id_observers: 124 o(value)
125 id = property(_get_id, _set_id, doc="The current session ID.") 126 127 timeout = 60 128 "Number of minutes after which to delete session data." 129 130 locked = False 131 """ 132 If True, this session instance has exclusive read/write access 133 to session data.""" 134 135 loaded = False 136 """ 137 If True, data has been retrieved from storage. This should happen 138 automatically on the first attempt to access session data.""" 139 140 clean_thread = None 141 "Class-level Monitor which calls self.clean_up." 142 143 clean_freq = 5 144 "The poll rate for expired session cleanup in minutes." 145 146 originalid = None 147 "The session id passed by the client. May be missing or unsafe." 148 149 missing = False 150 "True if the session requested by the client did not exist." 151 152 regenerated = False 153 """ 154 True if the application called session.regenerate(). This is not set by 155 internal calls to regenerate the session id.""" 156 157 debug = False 158 "If True, log debug information." 159 160 # --------------------- Session management methods --------------------- # 161
162 - def __init__(self, id=None, **kwargs):
163 self.id_observers = [] 164 self._data = {} 165 166 for k, v in kwargs.items(): 167 setattr(self, k, v) 168 169 self.originalid = id 170 self.missing = False 171 if id is None: 172 if self.debug: 173 cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS') 174 self._regenerate() 175 else: 176 self.id = id 177 if self._exists(): 178 if self.debug: 179 cherrypy.log('Set id to %s.' % id, 'TOOLS.SESSIONS') 180 else: 181 if self.debug: 182 cherrypy.log('Expired or malicious session %r; ' 183 'making a new one' % id, 'TOOLS.SESSIONS') 184 # Expired or malicious session. Make a new one. 185 # See https://bitbucket.org/cherrypy/cherrypy/issue/709. 186 self.id = None 187 self.missing = True 188 self._regenerate()
189
190 - def now(self):
191 """Generate the session specific concept of 'now'. 192 193 Other session providers can override this to use alternative, 194 possibly timezone aware, versions of 'now'. 195 """ 196 return datetime.datetime.now()
197
198 - def regenerate(self):
199 """Replace the current session (with a new id).""" 200 self.regenerated = True 201 self._regenerate()
202
203 - def _regenerate(self):
204 if self.id is not None: 205 if self.debug: 206 cherrypy.log( 207 'Deleting the existing session %r before ' 208 'regeneration.' % self.id, 209 'TOOLS.SESSIONS') 210 self.delete() 211 212 old_session_was_locked = self.locked 213 if old_session_was_locked: 214 self.release_lock() 215 if self.debug: 216 cherrypy.log('Old lock released.', 'TOOLS.SESSIONS') 217 218 self.id = None 219 while self.id is None: 220 self.id = self.generate_id() 221 # Assert that the generated id is not already stored. 222 if self._exists(): 223 self.id = None 224 if self.debug: 225 cherrypy.log('Set id to generated %s.' % self.id, 226 'TOOLS.SESSIONS') 227 228 if old_session_was_locked: 229 self.acquire_lock() 230 if self.debug: 231 cherrypy.log('Regenerated lock acquired.', 'TOOLS.SESSIONS')
232
233 - def clean_up(self):
234 """Clean up expired sessions.""" 235 pass
236
237 - def generate_id(self):
238 """Return a new session id.""" 239 return random20()
240
241 - def save(self):
242 """Save session data.""" 243 try: 244 # If session data has never been loaded then it's never been 245 # accessed: no need to save it 246 if self.loaded: 247 t = datetime.timedelta(seconds=self.timeout * 60) 248 expiration_time = self.now() + t 249 if self.debug: 250 cherrypy.log('Saving session %r with expiry %s' % 251 (self.id, expiration_time), 252 'TOOLS.SESSIONS') 253 self._save(expiration_time) 254 else: 255 if self.debug: 256 cherrypy.log( 257 'Skipping save of session %r (no session loaded).' % 258 self.id, 'TOOLS.SESSIONS') 259 finally: 260 if self.locked: 261 # Always release the lock if the user didn't release it 262 self.release_lock() 263 if self.debug: 264 cherrypy.log('Lock released after save.', 'TOOLS.SESSIONS')
265
266 - def load(self):
267 """Copy stored session data into this session instance.""" 268 data = self._load() 269 # data is either None or a tuple (session_data, expiration_time) 270 if data is None or data[1] < self.now(): 271 if self.debug: 272 cherrypy.log('Expired session %r, flushing data.' % self.id, 273 'TOOLS.SESSIONS') 274 self._data = {} 275 else: 276 if self.debug: 277 cherrypy.log('Data loaded for session %r.' % self.id, 278 'TOOLS.SESSIONS') 279 self._data = data[0] 280 self.loaded = True 281 282 # Stick the clean_thread in the class, not the instance. 283 # The instances are created and destroyed per-request. 284 cls = self.__class__ 285 if self.clean_freq and not cls.clean_thread: 286 # clean_up is an instancemethod and not a classmethod, 287 # so that tool config can be accessed inside the method. 288 t = cherrypy.process.plugins.Monitor( 289 cherrypy.engine, self.clean_up, self.clean_freq * 60, 290 name='Session cleanup') 291 t.subscribe() 292 cls.clean_thread = t 293 t.start() 294 if self.debug: 295 cherrypy.log('Started cleanup thread.', 'TOOLS.SESSIONS')
296
297 - def delete(self):
298 """Delete stored session data.""" 299 self._delete() 300 if self.debug: 301 cherrypy.log('Deleted session %s.' % self.id, 302 'TOOLS.SESSIONS')
303 304 # -------------------- Application accessor methods -------------------- # 305
306 - def __getitem__(self, key):
307 if not self.loaded: 308 self.load() 309 return self._data[key]
310
311 - def __setitem__(self, key, value):
312 if not self.loaded: 313 self.load() 314 self._data[key] = value
315
316 - def __delitem__(self, key):
317 if not self.loaded: 318 self.load() 319 del self._data[key]
320
321 - def pop(self, key, default=missing):
322 """Remove the specified key and return the corresponding value. 323 If key is not found, default is returned if given, 324 otherwise KeyError is raised. 325 """ 326 if not self.loaded: 327 self.load() 328 if default is missing: 329 return self._data.pop(key) 330 else: 331 return self._data.pop(key, default)
332
333 - def __contains__(self, key):
334 if not self.loaded: 335 self.load() 336 return key in self._data
337 338 if hasattr({}, 'has_key'):
339 - def has_key(self, key):
340 """D.has_key(k) -> True if D has a key k, else False.""" 341 if not self.loaded: 342 self.load() 343 return key in self._data
344
345 - def get(self, key, default=None):
346 """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" 347 if not self.loaded: 348 self.load() 349 return self._data.get(key, default)
350
351 - def update(self, d):
352 """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k].""" 353 if not self.loaded: 354 self.load() 355 self._data.update(d)
356
357 - def setdefault(self, key, default=None):
358 """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D.""" 359 if not self.loaded: 360 self.load() 361 return self._data.setdefault(key, default)
362
363 - def clear(self):
364 """D.clear() -> None. Remove all items from D.""" 365 if not self.loaded: 366 self.load() 367 self._data.clear()
368
369 - def keys(self):
370 """D.keys() -> list of D's keys.""" 371 if not self.loaded: 372 self.load() 373 return self._data.keys()
374
375 - def items(self):
376 """D.items() -> list of D's (key, value) pairs, as 2-tuples.""" 377 if not self.loaded: 378 self.load() 379 return self._data.items()
380
381 - def values(self):
382 """D.values() -> list of D's values.""" 383 if not self.loaded: 384 self.load() 385 return self._data.values()
386 387
388 -class RamSession(Session):
389 390 # Class-level objects. Don't rebind these! 391 cache = {} 392 locks = {} 393
394 - def clean_up(self):
395 """Clean up expired sessions.""" 396 now = self.now() 397 for id, (data, expiration_time) in copyitems(self.cache): 398 if expiration_time <= now: 399 try: 400 del self.cache[id] 401 except KeyError: 402 pass 403 try: 404 del self.locks[id] 405 except KeyError: 406 pass 407 408 # added to remove obsolete lock objects 409 for id in list(self.locks): 410 if id not in self.cache: 411 self.locks.pop(id, None)
412
413 - def _exists(self):
414 return self.id in self.cache
415
416 - def _load(self):
417 return self.cache.get(self.id)
418
419 - def _save(self, expiration_time):
420 self.cache[self.id] = (self._data, expiration_time)
421
422 - def _delete(self):
423 self.cache.pop(self.id, None)
424
425 - def acquire_lock(self):
426 """Acquire an exclusive lock on the currently-loaded session data.""" 427 self.locked = True 428 self.locks.setdefault(self.id, threading.RLock()).acquire()
429
430 - def release_lock(self):
431 """Release the lock on the currently-loaded session data.""" 432 self.locks[self.id].release() 433 self.locked = False
434
435 - def __len__(self):
436 """Return the number of active sessions.""" 437 return len(self.cache)
438 439
440 -class FileSession(Session):
441 442 """Implementation of the File backend for sessions 443 444 storage_path 445 The folder where session data will be saved. Each session 446 will be saved as pickle.dump(data, expiration_time) in its own file; 447 the filename will be self.SESSION_PREFIX + self.id. 448 449 lock_timeout 450 A timedelta or numeric seconds indicating how long 451 to block acquiring a lock. If None (default), acquiring a lock 452 will block indefinitely. 453 """ 454 455 SESSION_PREFIX = 'session-' 456 LOCK_SUFFIX = '.lock' 457 pickle_protocol = pickle.HIGHEST_PROTOCOL 458
459 - def __init__(self, id=None, **kwargs):
460 # The 'storage_path' arg is required for file-based sessions. 461 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) 462 kwargs.setdefault('lock_timeout', None) 463 464 Session.__init__(self, id=id, **kwargs) 465 466 # validate self.lock_timeout 467 if isinstance(self.lock_timeout, (int, float)): 468 self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout) 469 if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))): 470 raise ValueError("Lock timeout must be numeric seconds or " 471 "a timedelta instance.")
472
473 - def setup(cls, **kwargs):
474 """Set up the storage system for file-based sessions. 475 476 This should only be called once per process; this will be done 477 automatically when using sessions.init (as the built-in Tool does). 478 """ 479 # The 'storage_path' arg is required for file-based sessions. 480 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) 481 482 for k, v in kwargs.items(): 483 setattr(cls, k, v)
484 setup = classmethod(setup) 485
486 - def _get_file_path(self):
487 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id) 488 if not os.path.abspath(f).startswith(self.storage_path): 489 raise cherrypy.HTTPError(400, "Invalid session id in cookie.") 490 return f
491
492 - def _exists(self):
493 path = self._get_file_path() 494 return os.path.exists(path)
495
496 - def _load(self, path=None):
497 assert self.locked, ("The session load without being locked. " 498 "Check your tools' priority levels.") 499 if path is None: 500 path = self._get_file_path() 501 try: 502 f = open(path, "rb") 503 try: 504 return pickle.load(f) 505 finally: 506 f.close() 507 except (IOError, EOFError): 508 e = sys.exc_info()[1] 509 if self.debug: 510 cherrypy.log("Error loading the session pickle: %s" % 511 e, 'TOOLS.SESSIONS') 512 return None
513
514 - def _save(self, expiration_time):
515 assert self.locked, ("The session was saved without being locked. " 516 "Check your tools' priority levels.") 517 f = open(self._get_file_path(), "wb") 518 try: 519 pickle.dump((self._data, expiration_time), f, self.pickle_protocol) 520 finally: 521 f.close()
522
523 - def _delete(self):
524 assert self.locked, ("The session deletion without being locked. " 525 "Check your tools' priority levels.") 526 try: 527 os.unlink(self._get_file_path()) 528 except OSError: 529 pass
530
531 - def acquire_lock(self, path=None):
532 """Acquire an exclusive lock on the currently-loaded session data.""" 533 if path is None: 534 path = self._get_file_path() 535 path += self.LOCK_SUFFIX 536 checker = locking.LockChecker(self.id, self.lock_timeout) 537 while not checker.expired(): 538 try: 539 self.lock = lockfile.LockFile(path) 540 except lockfile.LockError: 541 time.sleep(0.1) 542 else: 543 break 544 self.locked = True 545 if self.debug: 546 cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
547
548 - def release_lock(self, path=None):
549 """Release the lock on the currently-loaded session data.""" 550 self.lock.release() 551 self.lock.remove() 552 self.locked = False
553
554 - def clean_up(self):
555 """Clean up expired sessions.""" 556 now = self.now() 557 # Iterate over all session files in self.storage_path 558 for fname in os.listdir(self.storage_path): 559 if (fname.startswith(self.SESSION_PREFIX) 560 and not fname.endswith(self.LOCK_SUFFIX)): 561 # We have a session file: lock and load it and check 562 # if it's expired. If it fails, nevermind. 563 path = os.path.join(self.storage_path, fname) 564 self.acquire_lock(path) 565 if self.debug: 566 # This is a bit of a hack, since we're calling clean_up 567 # on the first instance rather than the entire class, 568 # so depending on whether you have "debug" set on the 569 # path of the first session called, this may not run. 570 cherrypy.log('Cleanup lock acquired.', 'TOOLS.SESSIONS') 571 572 try: 573 contents = self._load(path) 574 # _load returns None on IOError 575 if contents is not None: 576 data, expiration_time = contents 577 if expiration_time < now: 578 # Session expired: deleting it 579 os.unlink(path) 580 finally: 581 self.release_lock(path)
582
583 - def __len__(self):
584 """Return the number of active sessions.""" 585 return len([fname for fname in os.listdir(self.storage_path) 586 if (fname.startswith(self.SESSION_PREFIX) 587 and not fname.endswith(self.LOCK_SUFFIX))])
588 589
590 -class PostgresqlSession(Session):
591 592 """ Implementation of the PostgreSQL backend for sessions. It assumes 593 a table like this:: 594 595 create table session ( 596 id varchar(40), 597 data text, 598 expiration_time timestamp 599 ) 600 601 You must provide your own get_db function. 602 """ 603 604 pickle_protocol = pickle.HIGHEST_PROTOCOL 605
606 - def __init__(self, id=None, **kwargs):
607 Session.__init__(self, id, **kwargs) 608 self.cursor = self.db.cursor()
609
610 - def setup(cls, **kwargs):
611 """Set up the storage system for Postgres-based sessions. 612 613 This should only be called once per process; this will be done 614 automatically when using sessions.init (as the built-in Tool does). 615 """ 616 for k, v in kwargs.items(): 617 setattr(cls, k, v) 618 619 self.db = self.get_db()
620 setup = classmethod(setup) 621
622 - def __del__(self):
623 if self.cursor: 624 self.cursor.close() 625 self.db.commit()
626
627 - def _exists(self):
628 # Select session data from table 629 self.cursor.execute('select data, expiration_time from session ' 630 'where id=%s', (self.id,)) 631 rows = self.cursor.fetchall() 632 return bool(rows)
633
634 - def _load(self):
635 # Select session data from table 636 self.cursor.execute('select data, expiration_time from session ' 637 'where id=%s', (self.id,)) 638 rows = self.cursor.fetchall() 639 if not rows: 640 return None 641 642 pickled_data, expiration_time = rows[0] 643 data = pickle.loads(pickled_data) 644 return data, expiration_time
645
646 - def _save(self, expiration_time):
647 pickled_data = pickle.dumps(self._data, self.pickle_protocol) 648 self.cursor.execute('update session set data = %s, ' 649 'expiration_time = %s where id = %s', 650 (pickled_data, expiration_time, self.id))
651
652 - def _delete(self):
653 self.cursor.execute('delete from session where id=%s', (self.id,))
654
655 - def acquire_lock(self):
656 """Acquire an exclusive lock on the currently-loaded session data.""" 657 # We use the "for update" clause to lock the row 658 self.locked = True 659 self.cursor.execute('select id from session where id=%s for update', 660 (self.id,)) 661 if self.debug: 662 cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
663
664 - def release_lock(self):
665 """Release the lock on the currently-loaded session data.""" 666 # We just close the cursor and that will remove the lock 667 # introduced by the "for update" clause 668 self.cursor.close() 669 self.locked = False
670
671 - def clean_up(self):
672 """Clean up expired sessions.""" 673 self.cursor.execute('delete from session where expiration_time < %s', 674 (self.now(),))
675 676
677 -class MemcachedSession(Session):
678 679 # The most popular memcached client for Python isn't thread-safe. 680 # Wrap all .get and .set operations in a single lock. 681 mc_lock = threading.RLock() 682 683 # This is a seperate set of locks per session id. 684 locks = {} 685 686 servers = ['127.0.0.1:11211'] 687
688 - def setup(cls, **kwargs):
689 """Set up the storage system for memcached-based sessions. 690 691 This should only be called once per process; this will be done 692 automatically when using sessions.init (as the built-in Tool does). 693 """ 694 for k, v in kwargs.items(): 695 setattr(cls, k, v) 696 697 import memcache 698 cls.cache = memcache.Client(cls.servers)
699 setup = classmethod(setup) 700
701 - def _get_id(self):
702 return self._id
703
704 - def _set_id(self, value):
705 # This encode() call is where we differ from the superclass. 706 # Memcache keys MUST be byte strings, not unicode. 707 if isinstance(value, unicodestr): 708 value = value.encode('utf-8') 709 710 self._id = value 711 for o in self.id_observers: 712 o(value)
713 id = property(_get_id, _set_id, doc="The current session ID.") 714
715 - def _exists(self):
716 self.mc_lock.acquire() 717 try: 718 return bool(self.cache.get(self.id)) 719 finally: 720 self.mc_lock.release()
721
722 - def _load(self):
723 self.mc_lock.acquire() 724 try: 725 return self.cache.get(self.id) 726 finally: 727 self.mc_lock.release()
728
729 - def _save(self, expiration_time):
730 # Send the expiration time as "Unix time" (seconds since 1/1/1970) 731 td = int(time.mktime(expiration_time.timetuple())) 732 self.mc_lock.acquire() 733 try: 734 if not self.cache.set(self.id, (self._data, expiration_time), td): 735 raise AssertionError( 736 "Session data for id %r not set." % self.id) 737 finally: 738 self.mc_lock.release()
739
740 - def _delete(self):
741 self.cache.delete(self.id)
742
743 - def acquire_lock(self):
744 """Acquire an exclusive lock on the currently-loaded session data.""" 745 self.locked = True 746 self.locks.setdefault(self.id, threading.RLock()).acquire() 747 if self.debug: 748 cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
749
750 - def release_lock(self):
751 """Release the lock on the currently-loaded session data.""" 752 self.locks[self.id].release() 753 self.locked = False
754
755 - def __len__(self):
756 """Return the number of active sessions.""" 757 raise NotImplementedError
758 759 760 # Hook functions (for CherryPy tools) 761
762 -def save():
763 """Save any changed session data.""" 764 765 if not hasattr(cherrypy.serving, "session"): 766 return 767 request = cherrypy.serving.request 768 response = cherrypy.serving.response 769 770 # Guard against running twice 771 if hasattr(request, "_sessionsaved"): 772 return 773 request._sessionsaved = True 774 775 if response.stream: 776 # If the body is being streamed, we have to save the data 777 # *after* the response has been written out 778 request.hooks.attach('on_end_request', cherrypy.session.save) 779 else: 780 # If the body is not being streamed, we save the data now 781 # (so we can release the lock). 782 if is_iterator(response.body): 783 response.collapse_body() 784 cherrypy.session.save()
785 save.failsafe = True 786 787
788 -def close():
789 """Close the session object for this request.""" 790 sess = getattr(cherrypy.serving, "session", None) 791 if getattr(sess, "locked", False): 792 # If the session is still locked we release the lock 793 sess.release_lock() 794 if sess.debug: 795 cherrypy.log('Lock released on close.', 'TOOLS.SESSIONS')
796 close.failsafe = True 797 close.priority = 90 798 799
800 -def init(storage_type='ram', path=None, path_header=None, name='session_id', 801 timeout=60, domain=None, secure=False, clean_freq=5, 802 persistent=True, httponly=False, debug=False, **kwargs):
803 """Initialize session object (using cookies). 804 805 storage_type 806 One of 'ram', 'file', 'postgresql', 'memcached'. This will be 807 used to look up the corresponding class in cherrypy.lib.sessions 808 globals. For example, 'file' will use the FileSession class. 809 810 path 811 The 'path' value to stick in the response cookie metadata. 812 813 path_header 814 If 'path' is None (the default), then the response 815 cookie 'path' will be pulled from request.headers[path_header]. 816 817 name 818 The name of the cookie. 819 820 timeout 821 The expiration timeout (in minutes) for the stored session data. 822 If 'persistent' is True (the default), this is also the timeout 823 for the cookie. 824 825 domain 826 The cookie domain. 827 828 secure 829 If False (the default) the cookie 'secure' value will not 830 be set. If True, the cookie 'secure' value will be set (to 1). 831 832 clean_freq (minutes) 833 The poll rate for expired session cleanup. 834 835 persistent 836 If True (the default), the 'timeout' argument will be used 837 to expire the cookie. If False, the cookie will not have an expiry, 838 and the cookie will be a "session cookie" which expires when the 839 browser is closed. 840 841 httponly 842 If False (the default) the cookie 'httponly' value will not be set. 843 If True, the cookie 'httponly' value will be set (to 1). 844 845 Any additional kwargs will be bound to the new Session instance, 846 and may be specific to the storage type. See the subclass of Session 847 you're using for more information. 848 """ 849 850 request = cherrypy.serving.request 851 852 # Guard against running twice 853 if hasattr(request, "_session_init_flag"): 854 return 855 request._session_init_flag = True 856 857 # Check if request came with a session ID 858 id = None 859 if name in request.cookie: 860 id = request.cookie[name].value 861 if debug: 862 cherrypy.log('ID obtained from request.cookie: %r' % id, 863 'TOOLS.SESSIONS') 864 865 # Find the storage class and call setup (first time only). 866 storage_class = storage_type.title() + 'Session' 867 storage_class = globals()[storage_class] 868 if not hasattr(cherrypy, "session"): 869 if hasattr(storage_class, "setup"): 870 storage_class.setup(**kwargs) 871 872 # Create and attach a new Session instance to cherrypy.serving. 873 # It will possess a reference to (and lock, and lazily load) 874 # the requested session data. 875 kwargs['timeout'] = timeout 876 kwargs['clean_freq'] = clean_freq 877 cherrypy.serving.session = sess = storage_class(id, **kwargs) 878 sess.debug = debug 879 880 def update_cookie(id): 881 """Update the cookie every time the session id changes.""" 882 cherrypy.serving.response.cookie[name] = id
883 sess.id_observers.append(update_cookie) 884 885 # Create cherrypy.session which will proxy to cherrypy.serving.session 886 if not hasattr(cherrypy, "session"): 887 cherrypy.session = cherrypy._ThreadLocalProxy('session') 888 889 if persistent: 890 cookie_timeout = timeout 891 else: 892 # See http://support.microsoft.com/kb/223799/EN-US/ 893 # and http://support.mozilla.com/en-US/kb/Cookies 894 cookie_timeout = None 895 set_response_cookie(path=path, path_header=path_header, name=name, 896 timeout=cookie_timeout, domain=domain, secure=secure, 897 httponly=httponly) 898 899 956 957
958 -def expire():
959 """Expire the current session cookie.""" 960 name = cherrypy.serving.request.config.get( 961 'tools.sessions.name', 'session_id') 962 one_year = 60 * 60 * 24 * 365 963 e = time.time() - one_year 964 cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)
965