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