Bundled cherrypy.
[smonitor.git] / monitor / cherrypy / lib / sessions.py
blob42c28009f86b9d821db9ec70bfd882dbe17be7a7
1 """Session implementation for CherryPy.
3 You need to edit your config file to use sessions. Here's an example::
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
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.
16 By default, the session ID is passed in a cookie, so the client's browser must
17 have cookies enabled for your site.
19 To set data for the current session, use
20 ``cherrypy.session['fieldname'] = 'fieldvalue'``;
21 to get data use ``cherrypy.session.get('fieldname')``.
23 ================
24 Locking sessions
25 ================
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.
34 =================
35 Expiring Sessions
36 =================
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.
42 ===========================
43 Session Fixation Protection
44 ===========================
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**.
54 ================
55 Sharing Sessions
56 ================
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.
65 ================
66 Expiration Dates
67 ================
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**.
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.
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 """
87 import datetime
88 import os
89 import random
90 import time
91 import threading
92 import types
93 from warnings import warn
95 import cherrypy
96 from cherrypy._cpcompat import copyitems, pickle, random20
97 from cherrypy.lib import httputil
100 missing = object()
102 class Session(object):
103 """A CherryPy dict-like Session object (one per request)."""
105 _id = None
107 id_observers = None
108 "A list of callbacks to which to pass new id's."
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.")
118 timeout = 60
119 "Number of minutes after which to delete session data."
121 locked = False
123 If True, this session instance has exclusive read/write access
124 to session data."""
126 loaded = False
128 If True, data has been retrieved from storage. This should happen
129 automatically on the first attempt to access session data."""
131 clean_thread = None
132 "Class-level Monitor which calls self.clean_up."
134 clean_freq = 5
135 "The poll rate for expired session cleanup in minutes."
137 originalid = None
138 "The session id passed by the client. May be missing or unsafe."
140 missing = False
141 "True if the session requested by the client did not exist."
143 regenerated = False
145 True if the application called session.regenerate(). This is not set by
146 internal calls to regenerate the session id."""
148 debug=False
150 def __init__(self, id=None, **kwargs):
151 self.id_observers = []
152 self._data = {}
154 for k, v in kwargs.items():
155 setattr(self, k, v)
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()
175 def regenerate(self):
176 """Replace the current session (with a new id)."""
177 self.regenerated = True
178 self._regenerate()
180 def _regenerate(self):
181 if self.id is not None:
182 self.delete()
184 old_session_was_locked = self.locked
185 if old_session_was_locked:
186 self.release_lock()
188 self.id = None
189 while self.id is None:
190 self.id = self.generate_id()
191 # Assert that the generated id is not already stored.
192 if self._exists():
193 self.id = None
195 if old_session_was_locked:
196 self.acquire_lock()
198 def clean_up(self):
199 """Clean up expired sessions."""
200 pass
202 def generate_id(self):
203 """Return a new session id."""
204 return random20()
206 def save(self):
207 """Save session data."""
208 try:
209 # If session data has never been loaded then it's never been
210 # accessed: no need to save it
211 if self.loaded:
212 t = datetime.timedelta(seconds = self.timeout * 60)
213 expiration_time = datetime.datetime.now() + t
214 if self.debug:
215 cherrypy.log('Saving with expiry %s' % expiration_time,
216 'TOOLS.SESSIONS')
217 self._save(expiration_time)
219 finally:
220 if self.locked:
221 # Always release the lock if the user didn't release it
222 self.release_lock()
224 def load(self):
225 """Copy stored session data into this session instance."""
226 data = self._load()
227 # data is either None or a tuple (session_data, expiration_time)
228 if data is None or data[1] < datetime.datetime.now():
229 if self.debug:
230 cherrypy.log('Expired session, flushing data', 'TOOLS.SESSIONS')
231 self._data = {}
232 else:
233 self._data = data[0]
234 self.loaded = True
236 # Stick the clean_thread in the class, not the instance.
237 # The instances are created and destroyed per-request.
238 cls = self.__class__
239 if self.clean_freq and not cls.clean_thread:
240 # clean_up is in instancemethod and not a classmethod,
241 # so that tool config can be accessed inside the method.
242 t = cherrypy.process.plugins.Monitor(
243 cherrypy.engine, self.clean_up, self.clean_freq * 60,
244 name='Session cleanup')
245 t.subscribe()
246 cls.clean_thread = t
247 t.start()
249 def delete(self):
250 """Delete stored session data."""
251 self._delete()
253 def __getitem__(self, key):
254 if not self.loaded: self.load()
255 return self._data[key]
257 def __setitem__(self, key, value):
258 if not self.loaded: self.load()
259 self._data[key] = value
261 def __delitem__(self, key):
262 if not self.loaded: self.load()
263 del self._data[key]
265 def pop(self, key, default=missing):
266 """Remove the specified key and return the corresponding value.
267 If key is not found, default is returned if given,
268 otherwise KeyError is raised.
270 if not self.loaded: self.load()
271 if default is missing:
272 return self._data.pop(key)
273 else:
274 return self._data.pop(key, default)
276 def __contains__(self, key):
277 if not self.loaded: self.load()
278 return key in self._data
280 def has_key(self, key):
281 """D.has_key(k) -> True if D has a key k, else False."""
282 if not self.loaded: self.load()
283 return key in self._data
285 def get(self, key, default=None):
286 """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
287 if not self.loaded: self.load()
288 return self._data.get(key, default)
290 def update(self, d):
291 """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
292 if not self.loaded: self.load()
293 self._data.update(d)
295 def setdefault(self, key, default=None):
296 """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D."""
297 if not self.loaded: self.load()
298 return self._data.setdefault(key, default)
300 def clear(self):
301 """D.clear() -> None. Remove all items from D."""
302 if not self.loaded: self.load()
303 self._data.clear()
305 def keys(self):
306 """D.keys() -> list of D's keys."""
307 if not self.loaded: self.load()
308 return self._data.keys()
310 def items(self):
311 """D.items() -> list of D's (key, value) pairs, as 2-tuples."""
312 if not self.loaded: self.load()
313 return self._data.items()
315 def values(self):
316 """D.values() -> list of D's values."""
317 if not self.loaded: self.load()
318 return self._data.values()
321 class RamSession(Session):
323 # Class-level objects. Don't rebind these!
324 cache = {}
325 locks = {}
327 def clean_up(self):
328 """Clean up expired sessions."""
329 now = datetime.datetime.now()
330 for id, (data, expiration_time) in copyitems(self.cache):
331 if expiration_time <= now:
332 try:
333 del self.cache[id]
334 except KeyError:
335 pass
336 try:
337 del self.locks[id]
338 except KeyError:
339 pass
341 def _exists(self):
342 return self.id in self.cache
344 def _load(self):
345 return self.cache.get(self.id)
347 def _save(self, expiration_time):
348 self.cache[self.id] = (self._data, expiration_time)
350 def _delete(self):
351 self.cache.pop(self.id, None)
353 def acquire_lock(self):
354 """Acquire an exclusive lock on the currently-loaded session data."""
355 self.locked = True
356 self.locks.setdefault(self.id, threading.RLock()).acquire()
358 def release_lock(self):
359 """Release the lock on the currently-loaded session data."""
360 self.locks[self.id].release()
361 self.locked = False
363 def __len__(self):
364 """Return the number of active sessions."""
365 return len(self.cache)
368 class FileSession(Session):
369 """Implementation of the File backend for sessions
371 storage_path
372 The folder where session data will be saved. Each session
373 will be saved as pickle.dump(data, expiration_time) in its own file;
374 the filename will be self.SESSION_PREFIX + self.id.
378 SESSION_PREFIX = 'session-'
379 LOCK_SUFFIX = '.lock'
380 pickle_protocol = pickle.HIGHEST_PROTOCOL
382 def __init__(self, id=None, **kwargs):
383 # The 'storage_path' arg is required for file-based sessions.
384 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
385 Session.__init__(self, id=id, **kwargs)
387 def setup(cls, **kwargs):
388 """Set up the storage system for file-based sessions.
390 This should only be called once per process; this will be done
391 automatically when using sessions.init (as the built-in Tool does).
393 # The 'storage_path' arg is required for file-based sessions.
394 kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
396 for k, v in kwargs.items():
397 setattr(cls, k, v)
399 # Warn if any lock files exist at startup.
400 lockfiles = [fname for fname in os.listdir(cls.storage_path)
401 if (fname.startswith(cls.SESSION_PREFIX)
402 and fname.endswith(cls.LOCK_SUFFIX))]
403 if lockfiles:
404 plural = ('', 's')[len(lockfiles) > 1]
405 warn("%s session lockfile%s found at startup. If you are "
406 "only running one process, then you may need to "
407 "manually delete the lockfiles found at %r."
408 % (len(lockfiles), plural, cls.storage_path))
409 setup = classmethod(setup)
411 def _get_file_path(self):
412 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
413 if not os.path.abspath(f).startswith(self.storage_path):
414 raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
415 return f
417 def _exists(self):
418 path = self._get_file_path()
419 return os.path.exists(path)
421 def _load(self, path=None):
422 if path is None:
423 path = self._get_file_path()
424 try:
425 f = open(path, "rb")
426 try:
427 return pickle.load(f)
428 finally:
429 f.close()
430 except (IOError, EOFError):
431 return None
433 def _save(self, expiration_time):
434 f = open(self._get_file_path(), "wb")
435 try:
436 pickle.dump((self._data, expiration_time), f, self.pickle_protocol)
437 finally:
438 f.close()
440 def _delete(self):
441 try:
442 os.unlink(self._get_file_path())
443 except OSError:
444 pass
446 def acquire_lock(self, path=None):
447 """Acquire an exclusive lock on the currently-loaded session data."""
448 if path is None:
449 path = self._get_file_path()
450 path += self.LOCK_SUFFIX
451 while True:
452 try:
453 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
454 except OSError:
455 time.sleep(0.1)
456 else:
457 os.close(lockfd)
458 break
459 self.locked = True
461 def release_lock(self, path=None):
462 """Release the lock on the currently-loaded session data."""
463 if path is None:
464 path = self._get_file_path()
465 os.unlink(path + self.LOCK_SUFFIX)
466 self.locked = False
468 def clean_up(self):
469 """Clean up expired sessions."""
470 now = datetime.datetime.now()
471 # Iterate over all session files in self.storage_path
472 for fname in os.listdir(self.storage_path):
473 if (fname.startswith(self.SESSION_PREFIX)
474 and not fname.endswith(self.LOCK_SUFFIX)):
475 # We have a session file: lock and load it and check
476 # if it's expired. If it fails, nevermind.
477 path = os.path.join(self.storage_path, fname)
478 self.acquire_lock(path)
479 try:
480 contents = self._load(path)
481 # _load returns None on IOError
482 if contents is not None:
483 data, expiration_time = contents
484 if expiration_time < now:
485 # Session expired: deleting it
486 os.unlink(path)
487 finally:
488 self.release_lock(path)
490 def __len__(self):
491 """Return the number of active sessions."""
492 return len([fname for fname in os.listdir(self.storage_path)
493 if (fname.startswith(self.SESSION_PREFIX)
494 and not fname.endswith(self.LOCK_SUFFIX))])
497 class PostgresqlSession(Session):
498 """ Implementation of the PostgreSQL backend for sessions. It assumes
499 a table like this::
501 create table session (
502 id varchar(40),
503 data text,
504 expiration_time timestamp
507 You must provide your own get_db function.
510 pickle_protocol = pickle.HIGHEST_PROTOCOL
512 def __init__(self, id=None, **kwargs):
513 Session.__init__(self, id, **kwargs)
514 self.cursor = self.db.cursor()
516 def setup(cls, **kwargs):
517 """Set up the storage system for Postgres-based sessions.
519 This should only be called once per process; this will be done
520 automatically when using sessions.init (as the built-in Tool does).
522 for k, v in kwargs.items():
523 setattr(cls, k, v)
525 self.db = self.get_db()
526 setup = classmethod(setup)
528 def __del__(self):
529 if self.cursor:
530 self.cursor.close()
531 self.db.commit()
533 def _exists(self):
534 # Select session data from table
535 self.cursor.execute('select data, expiration_time from session '
536 'where id=%s', (self.id,))
537 rows = self.cursor.fetchall()
538 return bool(rows)
540 def _load(self):
541 # Select session data from table
542 self.cursor.execute('select data, expiration_time from session '
543 'where id=%s', (self.id,))
544 rows = self.cursor.fetchall()
545 if not rows:
546 return None
548 pickled_data, expiration_time = rows[0]
549 data = pickle.loads(pickled_data)
550 return data, expiration_time
552 def _save(self, expiration_time):
553 pickled_data = pickle.dumps(self._data, self.pickle_protocol)
554 self.cursor.execute('update session set data = %s, '
555 'expiration_time = %s where id = %s',
556 (pickled_data, expiration_time, self.id))
558 def _delete(self):
559 self.cursor.execute('delete from session where id=%s', (self.id,))
561 def acquire_lock(self):
562 """Acquire an exclusive lock on the currently-loaded session data."""
563 # We use the "for update" clause to lock the row
564 self.locked = True
565 self.cursor.execute('select id from session where id=%s for update',
566 (self.id,))
568 def release_lock(self):
569 """Release the lock on the currently-loaded session data."""
570 # We just close the cursor and that will remove the lock
571 # introduced by the "for update" clause
572 self.cursor.close()
573 self.locked = False
575 def clean_up(self):
576 """Clean up expired sessions."""
577 self.cursor.execute('delete from session where expiration_time < %s',
578 (datetime.datetime.now(),))
581 class MemcachedSession(Session):
583 # The most popular memcached client for Python isn't thread-safe.
584 # Wrap all .get and .set operations in a single lock.
585 mc_lock = threading.RLock()
587 # This is a seperate set of locks per session id.
588 locks = {}
590 servers = ['127.0.0.1:11211']
592 def setup(cls, **kwargs):
593 """Set up the storage system for memcached-based sessions.
595 This should only be called once per process; this will be done
596 automatically when using sessions.init (as the built-in Tool does).
598 for k, v in kwargs.items():
599 setattr(cls, k, v)
601 import memcache
602 cls.cache = memcache.Client(cls.servers)
603 setup = classmethod(setup)
605 def _exists(self):
606 self.mc_lock.acquire()
607 try:
608 return bool(self.cache.get(self.id))
609 finally:
610 self.mc_lock.release()
612 def _load(self):
613 self.mc_lock.acquire()
614 try:
615 return self.cache.get(self.id)
616 finally:
617 self.mc_lock.release()
619 def _save(self, expiration_time):
620 # Send the expiration time as "Unix time" (seconds since 1/1/1970)
621 td = int(time.mktime(expiration_time.timetuple()))
622 self.mc_lock.acquire()
623 try:
624 if not self.cache.set(self.id, (self._data, expiration_time), td):
625 raise AssertionError("Session data for id %r not set." % self.id)
626 finally:
627 self.mc_lock.release()
629 def _delete(self):
630 self.cache.delete(self.id)
632 def acquire_lock(self):
633 """Acquire an exclusive lock on the currently-loaded session data."""
634 self.locked = True
635 self.locks.setdefault(self.id, threading.RLock()).acquire()
637 def release_lock(self):
638 """Release the lock on the currently-loaded session data."""
639 self.locks[self.id].release()
640 self.locked = False
642 def __len__(self):
643 """Return the number of active sessions."""
644 raise NotImplementedError
647 # Hook functions (for CherryPy tools)
649 def save():
650 """Save any changed session data."""
652 if not hasattr(cherrypy.serving, "session"):
653 return
654 request = cherrypy.serving.request
655 response = cherrypy.serving.response
657 # Guard against running twice
658 if hasattr(request, "_sessionsaved"):
659 return
660 request._sessionsaved = True
662 if response.stream:
663 # If the body is being streamed, we have to save the data
664 # *after* the response has been written out
665 request.hooks.attach('on_end_request', cherrypy.session.save)
666 else:
667 # If the body is not being streamed, we save the data now
668 # (so we can release the lock).
669 if isinstance(response.body, types.GeneratorType):
670 response.collapse_body()
671 cherrypy.session.save()
672 save.failsafe = True
674 def close():
675 """Close the session object for this request."""
676 sess = getattr(cherrypy.serving, "session", None)
677 if getattr(sess, "locked", False):
678 # If the session is still locked we release the lock
679 sess.release_lock()
680 close.failsafe = True
681 close.priority = 90
684 def init(storage_type='ram', path=None, path_header=None, name='session_id',
685 timeout=60, domain=None, secure=False, clean_freq=5,
686 persistent=True, debug=False, **kwargs):
687 """Initialize session object (using cookies).
689 storage_type
690 One of 'ram', 'file', 'postgresql'. This will be used
691 to look up the corresponding class in cherrypy.lib.sessions
692 globals. For example, 'file' will use the FileSession class.
694 path
695 The 'path' value to stick in the response cookie metadata.
697 path_header
698 If 'path' is None (the default), then the response
699 cookie 'path' will be pulled from request.headers[path_header].
701 name
702 The name of the cookie.
704 timeout
705 The expiration timeout (in minutes) for the stored session data.
706 If 'persistent' is True (the default), this is also the timeout
707 for the cookie.
709 domain
710 The cookie domain.
712 secure
713 If False (the default) the cookie 'secure' value will not
714 be set. If True, the cookie 'secure' value will be set (to 1).
716 clean_freq (minutes)
717 The poll rate for expired session cleanup.
719 persistent
720 If True (the default), the 'timeout' argument will be used
721 to expire the cookie. If False, the cookie will not have an expiry,
722 and the cookie will be a "session cookie" which expires when the
723 browser is closed.
725 Any additional kwargs will be bound to the new Session instance,
726 and may be specific to the storage type. See the subclass of Session
727 you're using for more information.
730 request = cherrypy.serving.request
732 # Guard against running twice
733 if hasattr(request, "_session_init_flag"):
734 return
735 request._session_init_flag = True
737 # Check if request came with a session ID
738 id = None
739 if name in request.cookie:
740 id = request.cookie[name].value
741 if debug:
742 cherrypy.log('ID obtained from request.cookie: %r' % id,
743 'TOOLS.SESSIONS')
745 # Find the storage class and call setup (first time only).
746 storage_class = storage_type.title() + 'Session'
747 storage_class = globals()[storage_class]
748 if not hasattr(cherrypy, "session"):
749 if hasattr(storage_class, "setup"):
750 storage_class.setup(**kwargs)
752 # Create and attach a new Session instance to cherrypy.serving.
753 # It will possess a reference to (and lock, and lazily load)
754 # the requested session data.
755 kwargs['timeout'] = timeout
756 kwargs['clean_freq'] = clean_freq
757 cherrypy.serving.session = sess = storage_class(id, **kwargs)
758 sess.debug = debug
759 def update_cookie(id):
760 """Update the cookie every time the session id changes."""
761 cherrypy.serving.response.cookie[name] = id
762 sess.id_observers.append(update_cookie)
764 # Create cherrypy.session which will proxy to cherrypy.serving.session
765 if not hasattr(cherrypy, "session"):
766 cherrypy.session = cherrypy._ThreadLocalProxy('session')
768 if persistent:
769 cookie_timeout = timeout
770 else:
771 # See http://support.microsoft.com/kb/223799/EN-US/
772 # and http://support.mozilla.com/en-US/kb/Cookies
773 cookie_timeout = None
774 set_response_cookie(path=path, path_header=path_header, name=name,
775 timeout=cookie_timeout, domain=domain, secure=secure)
778 def set_response_cookie(path=None, path_header=None, name='session_id',
779 timeout=60, domain=None, secure=False):
780 """Set a response cookie for the client.
782 path
783 the 'path' value to stick in the response cookie metadata.
785 path_header
786 if 'path' is None (the default), then the response
787 cookie 'path' will be pulled from request.headers[path_header].
789 name
790 the name of the cookie.
792 timeout
793 the expiration timeout for the cookie. If 0 or other boolean
794 False, no 'expires' param will be set, and the cookie will be a
795 "session cookie" which expires when the browser is closed.
797 domain
798 the cookie domain.
800 secure
801 if False (the default) the cookie 'secure' value will not
802 be set. If True, the cookie 'secure' value will be set (to 1).
805 # Set response cookie
806 cookie = cherrypy.serving.response.cookie
807 cookie[name] = cherrypy.serving.session.id
808 cookie[name]['path'] = (path or cherrypy.serving.request.headers.get(path_header)
809 or '/')
811 # We'd like to use the "max-age" param as indicated in
812 # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't
813 # save it to disk and the session is lost if people close
814 # the browser. So we have to use the old "expires" ... sigh ...
815 ## cookie[name]['max-age'] = timeout * 60
816 if timeout:
817 e = time.time() + (timeout * 60)
818 cookie[name]['expires'] = httputil.HTTPDate(e)
819 if domain is not None:
820 cookie[name]['domain'] = domain
821 if secure:
822 cookie[name]['secure'] = 1
825 def expire():
826 """Expire the current session cookie."""
827 name = cherrypy.serving.request.config.get('tools.sessions.name', 'session_id')
828 one_year = 60 * 60 * 24 * 365
829 e = time.time() - one_year
830 cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e)