| 30 | | _session_last_clean_up_time = datetime.datetime.now() |
|---|
| 31 | | _session_data_holder = {} # Needed for RAM sessions only |
|---|
| 32 | | _session_lock_dict = {} # Needed for RAM sessions only |
|---|
| 33 | | |
|---|
| 34 | | |
|---|
| 35 | | def generate_id(self=None): |
|---|
| 36 | | """Return a new session id""" |
|---|
| 37 | | return sha.new('%s' % random.random()).hexdigest() |
|---|
| 38 | | |
|---|
| 39 | | |
|---|
| 40 | | def noop(self, data=None): pass |
|---|
| 41 | | |
|---|
| 42 | | class Session: |
|---|
| 43 | | """A CherryPy Session object (one per request). |
|---|
| 44 | | |
|---|
| 45 | | timeout: timeout delay for the session |
|---|
| 46 | | locking: mechanism used to lock the session ('implicit' or 'explicit') |
|---|
| 47 | | storage (instance of the class implementing the backend) |
|---|
| 48 | | data: dictionary containing the actual session data |
|---|
| 49 | | id: current session ID |
|---|
| 50 | | expiration_time: date/time when the current session will expire |
|---|
| | 24 | |
|---|
| | 25 | class Session(object): |
|---|
| | 26 | """A CherryPy dict-like Session object (one per request). |
|---|
| | 27 | |
|---|
| | 28 | id: current session ID. |
|---|
| | 29 | expiration_time (datetime): when the current session will expire. |
|---|
| | 30 | timeout (minutes): used to calculate expiration_time from now. |
|---|
| | 31 | clean_freq (minutes): the poll rate for expired session cleanup. |
|---|
| | 32 | locked: If True, this session instance has exclusive read/write access |
|---|
| | 33 | to session data. |
|---|
| | 34 | loaded: If True, data has been retrieved from storage. This should |
|---|
| | 35 | happen automatically on the first attempt to access session data. |
|---|
| 77 | | self.generate_id = generate_id |
|---|
| 78 | | self.on_create = noop |
|---|
| 79 | | self.on_renew = noop |
|---|
| 80 | | self.on_delete = noop |
|---|
| 81 | | |
|---|
| 82 | | def init(self): |
|---|
| 83 | | # People can set their own custom class |
|---|
| 84 | | # through tools.sessions.storage_class |
|---|
| 85 | | if self.storage_class is None: |
|---|
| 86 | | self.storage_class = globals()[self.storage_type.title() + 'Storage'] |
|---|
| 87 | | self.storage = self.storage_class() |
|---|
| 88 | | |
|---|
| 89 | | now = datetime.datetime.now() |
|---|
| 90 | | # Check if we need to clean up old sessions |
|---|
| 91 | | global _session_last_clean_up_time |
|---|
| 92 | | clean_up_delay = datetime.timedelta(seconds = self.clean_up_delay * 60) |
|---|
| 93 | | if _session_last_clean_up_time + clean_up_delay < now: |
|---|
| 94 | | _session_last_clean_up_time = now |
|---|
| 95 | | # Run clean_up in other thread to avoid blocking this request |
|---|
| 96 | | thread.start_new_thread(self.storage.clean_up, (self,)) |
|---|
| 97 | | |
|---|
| 98 | | self.data = {} |
|---|
| 99 | | |
|---|
| 100 | | if self.cookie_path is None: |
|---|
| 101 | | if self.cookie_path_from_header is not None: |
|---|
| 102 | | geth = cherrypy.request.headers.get |
|---|
| 103 | | self.cookie_path = geth(self.cookie_path_from_header, None) |
|---|
| 104 | | if self.cookie_path is None: |
|---|
| 105 | | self.cookie_path = '/' |
|---|
| 106 | | |
|---|
| 107 | | # Check if request came with a session ID |
|---|
| 108 | | if self.cookie_name in cherrypy.request.simple_cookie: |
|---|
| 109 | | # It did: we mark the data as needing to be loaded |
|---|
| 110 | | self.id = cherrypy.request.simple_cookie[self.cookie_name].value |
|---|
| 111 | | |
|---|
| 112 | | # If using implicit locking, acquire lock |
|---|
| 113 | | if self.locking == 'implicit': |
|---|
| 114 | | self.data['_id'] = self.id |
|---|
| 115 | | self.storage.acquire_lock() |
|---|
| 116 | | else: |
|---|
| 117 | | # No id yet |
|---|
| 118 | | self.id = self.generate_id() |
|---|
| 119 | | self.data['_id'] = self.id |
|---|
| 120 | | self.on_create(self.data) |
|---|
| 121 | | |
|---|
| 122 | | # Set response cookie |
|---|
| 123 | | cookie = cherrypy.response.simple_cookie |
|---|
| 124 | | cookie[self.cookie_name] = self.id |
|---|
| 125 | | cookie[self.cookie_name]['path'] = self.cookie_path |
|---|
| 126 | | # We'd like to use the "max-age" param as |
|---|
| 127 | | # http://www.faqs.org/rfcs/rfc2109.html indicates but IE doesn't |
|---|
| 128 | | # save it to disk and the session is lost if people close |
|---|
| 129 | | # the browser |
|---|
| 130 | | # So we have to use the old "expires" ... sigh ... |
|---|
| 131 | | #cookie[cookie_name]['max-age'] = self.timeout * 60 |
|---|
| 132 | | if self.timeout: |
|---|
| 133 | | expiry = time.time() + (self.timeout * 60) |
|---|
| 134 | | cookie[self.cookie_name]['expires'] = http.HTTPDate(expiry) |
|---|
| 135 | | if self.cookie_domain is not None: |
|---|
| 136 | | cookie[self.cookie_name]['domain'] = self.cookie_domain |
|---|
| 137 | | if self.cookie_secure is True: |
|---|
| 138 | | cookie[self.cookie_name]['secure'] = 1 |
|---|
| | 45 | if id is None: |
|---|
| | 46 | id = self.generate_id() |
|---|
| | 47 | self.id = id |
|---|
| | 48 | |
|---|
| | 49 | def clean_cycle(self): |
|---|
| | 50 | """Clean up expired sessions at regular intervals.""" |
|---|
| | 51 | # clean_thread is both a cancelable Timer and a flag. |
|---|
| | 52 | if self.clean_thread: |
|---|
| | 53 | self.clean_up() |
|---|
| | 54 | t = threading.Timer(self.clean_freq, self.clean_cycle) |
|---|
| | 55 | self.__class__.clean_thread = t |
|---|
| | 56 | t.start() |
|---|
| | 57 | |
|---|
| | 58 | def clean_interrupt(cls): |
|---|
| | 59 | """Stop the expired-session cleaning cycle.""" |
|---|
| | 60 | if cls.clean_thread: |
|---|
| | 61 | cls.clean_thread.cancel() |
|---|
| | 62 | cls.clean_thread = None |
|---|
| | 63 | clean_interrupt = classmethod(clean_interrupt) |
|---|
| | 64 | |
|---|
| | 65 | def clean_up(self): |
|---|
| | 66 | """Clean up expired sessions.""" |
|---|
| | 67 | pass |
|---|
| | 68 | |
|---|
| | 69 | def generate_id(self): |
|---|
| | 70 | """Return a new session id.""" |
|---|
| | 71 | return sha.new('%s' % random.random()).hexdigest() |
|---|
| 153 | | self.saved = True |
|---|
| 154 | | |
|---|
| 155 | | |
|---|
| 156 | | class SessionDeadlockError(Exception): |
|---|
| 157 | | """The session could not acquire a lock after a certain time""" |
|---|
| 158 | | pass |
|---|
| 159 | | |
|---|
| 160 | | |
|---|
| 161 | | class SessionNotEnabledError(Exception): |
|---|
| 162 | | """User forgot to set tools.sessions.on to True""" |
|---|
| 163 | | pass |
|---|
| 164 | | |
|---|
| 165 | | class SessionStoragePathError(Exception): |
|---|
| 166 | | """User set storage_type to file but forgot to set the storage_path""" |
|---|
| 167 | | pass |
|---|
| 168 | | |
|---|
| 169 | | |
|---|
| 170 | | class RamStorage: |
|---|
| 171 | | """ Implementation of the RAM backend for sessions """ |
|---|
| 172 | | |
|---|
| 173 | | def load(self, id): |
|---|
| 174 | | return _session_data_holder.get(id) |
|---|
| 175 | | |
|---|
| 176 | | def save(self, id, data, expiration_time): |
|---|
| 177 | | _session_data_holder[id] = (data, expiration_time) |
|---|
| | 97 | cls = self.__class__ |
|---|
| | 98 | if not cls.clean_thread: |
|---|
| | 99 | cherrypy.engine.on_stop_engine_list.append(cls.clean_interrupt) |
|---|
| | 100 | # Use the instance to call clean_cycle so tool config |
|---|
| | 101 | # can be accessed inside the method. |
|---|
| | 102 | cls.clean_thread = t = threading.Timer(self.clean_freq, |
|---|
| | 103 | self.clean_cycle) |
|---|
| | 104 | t.start() |
|---|
| | 105 | |
|---|
| | 106 | def __getitem__(self, key): |
|---|
| | 107 | if not self.loaded: self.load() |
|---|
| | 108 | return self._data[key] |
|---|
| | 109 | |
|---|
| | 110 | def __setitem__(self, key, value): |
|---|
| | 111 | if not self.loaded: self.load() |
|---|
| | 112 | self._data[key] = value |
|---|
| | 113 | |
|---|
| | 114 | def __delitem__(self, key): |
|---|
| | 115 | if not self.loaded: self.load() |
|---|
| | 116 | del self._data[key] |
|---|
| | 117 | |
|---|
| | 118 | def __contains__(self, key): |
|---|
| | 119 | if not self.loaded: self.load() |
|---|
| | 120 | return key in self._data |
|---|
| | 121 | |
|---|
| | 122 | def has_key(self, key): |
|---|
| | 123 | if not self.loaded: self.load() |
|---|
| | 124 | return self._data.has_key(key) |
|---|
| | 125 | |
|---|
| | 126 | def get(self, key, default=None): |
|---|
| | 127 | if not self.loaded: self.load() |
|---|
| | 128 | return self._data.get(key, default) |
|---|
| | 129 | |
|---|
| | 130 | def update(self, d): |
|---|
| | 131 | if not self.loaded: self.load() |
|---|
| | 132 | self._data.update(d) |
|---|
| | 133 | |
|---|
| | 134 | def setdefault(self, key, default=None): |
|---|
| | 135 | if not self.loaded: self.load() |
|---|
| | 136 | return self._data.setdefault(key, default) |
|---|
| | 137 | |
|---|
| | 138 | def clear(self): |
|---|
| | 139 | if not self.loaded: self.load() |
|---|
| | 140 | self._data.clear() |
|---|
| | 141 | |
|---|
| | 142 | def keys(self): |
|---|
| | 143 | if not self.loaded: self.load() |
|---|
| | 144 | return self._data.keys() |
|---|
| | 145 | |
|---|
| | 146 | def items(self): |
|---|
| | 147 | if not self.loaded: self.load() |
|---|
| | 148 | return self._data.items() |
|---|
| | 149 | |
|---|
| | 150 | def values(self): |
|---|
| | 151 | if not self.loaded: self.load() |
|---|
| | 152 | return self._data.values() |
|---|
| | 153 | |
|---|
| | 154 | |
|---|
| | 155 | class RamSession(Session): |
|---|
| | 156 | |
|---|
| | 157 | # Class-level objects. Don't rebind these! |
|---|
| | 158 | cache = {} |
|---|
| | 159 | locks = {} |
|---|
| | 160 | |
|---|
| | 161 | def clean_up(self): |
|---|
| | 162 | """Clean up expired sessions.""" |
|---|
| | 163 | now = datetime.datetime.now() |
|---|
| | 164 | for id, (data, expiration_time) in self.cache.items(): |
|---|
| | 165 | if expiration_time < now: |
|---|
| | 166 | try: |
|---|
| | 167 | del self.cache[id] |
|---|
| | 168 | except KeyError: |
|---|
| | 169 | pass |
|---|
| | 170 | |
|---|
| | 171 | def _load(self): |
|---|
| | 172 | return self.cache.get(self.id) |
|---|
| | 173 | |
|---|
| | 174 | def _save(self, expiration_time): |
|---|
| | 175 | self.cache[self.id] = (self._data, expiration_time) |
|---|
| 196 | | _session_lock_dict[cherrypy.session['_id']].release() |
|---|
| 197 | | cherrypy.request._session.locked = False |
|---|
| 198 | | |
|---|
| 199 | | def clean_up(self, sess): |
|---|
| 200 | | to_be_deleted = [] |
|---|
| 201 | | now = datetime.datetime.now() |
|---|
| 202 | | for id, (data, expiration_time) in _session_data_holder.iteritems(): |
|---|
| 203 | | if expiration_time < now: |
|---|
| 204 | | to_be_deleted.append(id) |
|---|
| 205 | | for id in to_be_deleted: |
|---|
| 206 | | try: |
|---|
| 207 | | deleted_session = _session_data_holder[id] |
|---|
| 208 | | del _session_data_holder[id] |
|---|
| 209 | | sess.on_delete(deleted_session) |
|---|
| 210 | | except KeyError: |
|---|
| 211 | | # The session probably got deleted by a concurrent thread |
|---|
| 212 | | # Safe to ignore this case |
|---|
| 213 | | pass |
|---|
| 214 | | |
|---|
| 215 | | |
|---|
| 216 | | class FileStorage: |
|---|
| | 182 | self.locks[self.id].release() |
|---|
| | 183 | self.locked = False |
|---|
| | 184 | |
|---|
| | 185 | |
|---|
| | 186 | class FileSession(Session): |
|---|
| 232 | | def save(self, id, data, expiration_time): |
|---|
| 233 | | file_path = self._get_file_path(id) |
|---|
| 234 | | f = open(file_path, "wb") |
|---|
| 235 | | pickle.dump((data, expiration_time), f) |
|---|
| 236 | | f.close() |
|---|
| 237 | | |
|---|
| 238 | | def acquire_lock(self): |
|---|
| 239 | | if not cherrypy.request._session.locked: |
|---|
| 240 | | file_path = self._get_file_path(cherrypy.session.id) |
|---|
| 241 | | self._lock_file(file_path + self.LOCK_SUFFIX) |
|---|
| 242 | | cherrypy.request._session.locked = True |
|---|
| 243 | | |
|---|
| 244 | | def release_lock(self): |
|---|
| 245 | | file_path = self._get_file_path(cherrypy.session.id) |
|---|
| 246 | | self._unlock_file(file_path + self.LOCK_SUFFIX) |
|---|
| 247 | | cherrypy.request._session.locked = False |
|---|
| 248 | | |
|---|
| 249 | | def clean_up(self, sess): |
|---|
| 250 | | storage_path = getattr(sess, "storage_path") |
|---|
| 251 | | if storage_path is None: |
|---|
| 252 | | return |
|---|
| 253 | | now = datetime.datetime.now() |
|---|
| 254 | | # Iterate over all files in the dir/ and exclude non session files |
|---|
| 255 | | # and lock files |
|---|
| 256 | | for fname in os.listdir(storage_path): |
|---|
| 257 | | if (fname.startswith(self.SESSION_PREFIX) |
|---|
| 258 | | and not fname.endswith(self.LOCK_SUFFIX)): |
|---|
| 259 | | # We have a session file: try to load it and check |
|---|
| 260 | | # if it's expired. If it fails, nevermind. |
|---|
| 261 | | file_path = os.path.join(storage_path, fname) |
|---|
| 262 | | try: |
|---|
| 263 | | f = open(file_path, "rb") |
|---|
| 264 | | data, expiration_time = pickle.load(f) |
|---|
| 265 | | f.close() |
|---|
| 266 | | if expiration_time < now: |
|---|
| 267 | | # Session expired: deleting it |
|---|
| 268 | | id = fname[len(self.SESSION_PREFIX):] |
|---|
| 269 | | sess.on_delete(data) |
|---|
| 270 | | os.unlink(file_path) |
|---|
| 271 | | except: |
|---|
| 272 | | # We can't access the file ... nevermind |
|---|
| 273 | | pass |
|---|
| 274 | | |
|---|
| 275 | | def _get_file_path(self, id): |
|---|
| 276 | | storage_path = getattr(cherrypy.request._session, "storage_path") |
|---|
| 277 | | if storage_path is None: |
|---|
| 278 | | raise SessionStoragePathError() |
|---|
| 279 | | fileName = self.SESSION_PREFIX + id |
|---|
| 280 | | file_path = os.path.join(storage_path, fileName) |
|---|
| 281 | | return file_path |
|---|
| 282 | | |
|---|
| 283 | | def _lock_file(self, path): |
|---|
| 284 | | timeout = cherrypy.request._session.deadlock_timeout |
|---|
| 285 | | startTime = time.time() |
|---|
| | 207 | def _save(self, expiration_time): |
|---|
| | 208 | f = open(self._get_file_path(), "wb") |
|---|
| | 209 | try: |
|---|
| | 210 | pickle.dump((self._data, expiration_time), f) |
|---|
| | 211 | finally: |
|---|
| | 212 | f.close() |
|---|
| | 213 | |
|---|
| | 214 | def acquire_lock(self, path=None): |
|---|
| | 215 | if path is None: |
|---|
| | 216 | path = self._get_file_path() |
|---|
| | 217 | path += self.LOCK_SUFFIX |
|---|
| 296 | | |
|---|
| 297 | | def _unlock_file(self, path): |
|---|
| 298 | | os.unlink(path) |
|---|
| 299 | | |
|---|
| 300 | | |
|---|
| 301 | | class PostgreSQLStorage: |
|---|
| | 226 | self.locked = True |
|---|
| | 227 | |
|---|
| | 228 | def release_lock(self, path=None): |
|---|
| | 229 | if path is None: |
|---|
| | 230 | path = self._get_file_path() |
|---|
| | 231 | os.unlink(path + self.LOCK_SUFFIX) |
|---|
| | 232 | self.locked = False |
|---|
| | 233 | |
|---|
| | 234 | def clean_up(self): |
|---|
| | 235 | """Clean up expired sessions.""" |
|---|
| | 236 | now = datetime.datetime.now() |
|---|
| | 237 | # Iterate over all session files in self.storage_path |
|---|
| | 238 | for fname in os.listdir(self.storage_path): |
|---|
| | 239 | if (fname.startswith(self.SESSION_PREFIX) |
|---|
| | 240 | and not fname.endswith(self.LOCK_SUFFIX)): |
|---|
| | 241 | # We have a session file: lock and load it and check |
|---|
| | 242 | # if it's expired. If it fails, nevermind. |
|---|
| | 243 | path = os.path.join(self.storage_path, fname) |
|---|
| | 244 | self.acquire_lock(path) |
|---|
| | 245 | try: |
|---|
| | 246 | contents = self._load(path) |
|---|
| | 247 | # _load returns None on IOError |
|---|
| | 248 | if contents is not None: |
|---|
| | 249 | data, expiration_time = contents |
|---|
| | 250 | if expiration_time < now: |
|---|
| | 251 | # Session expired: deleting it |
|---|
| | 252 | os.unlink(path) |
|---|
| | 253 | finally: |
|---|
| | 254 | self.release_lock(path) |
|---|
| | 255 | |
|---|
| | 256 | |
|---|
| | 257 | class PostgresqlSession(Session): |
|---|
| 332 | | return (data, expiration_time) |
|---|
| 333 | | |
|---|
| 334 | | def save(self, id, data, expiration_time): |
|---|
| 335 | | # Try to delete session if it was already there |
|---|
| 336 | | self.cursor.execute( |
|---|
| 337 | | 'delete from session where id=%s', |
|---|
| 338 | | (id,)) |
|---|
| 339 | | # Pickle data |
|---|
| 340 | | pickled_data = pickle.dumps(data) |
|---|
| 341 | | # Insert new session data |
|---|
| | 289 | return data, expiration_time |
|---|
| | 290 | |
|---|
| | 291 | def _save(self, expiration_time): |
|---|
| | 292 | self.cursor.execute('delete from session where id=%s', (self.id,)) |
|---|
| | 293 | pickled_data = pickle.dumps(self._data) |
|---|
| 356 | | self.cursor = None |
|---|
| 357 | | |
|---|
| 358 | | def clean_up(self, sess): |
|---|
| 359 | | now = datetime.datetime.now() |
|---|
| 360 | | self.cursor.execute( |
|---|
| 361 | | 'select data from session where expiration_time < %s', |
|---|
| 362 | | (now,)) |
|---|
| 363 | | rows = self.cursor.fetchall() |
|---|
| 364 | | for row in rows: |
|---|
| 365 | | sess.on_delete(row[0]) |
|---|
| 366 | | self.cursor.execute( |
|---|
| 367 | | 'delete from session where expiration_time < %s', |
|---|
| 368 | | (now,)) |
|---|
| 369 | | |
|---|
| 370 | | |
|---|
| 371 | | # Users access sessions through cherrypy.session, but we want this |
|---|
| 372 | | # to be thread-specific so we use a wrapper that forwards calls |
|---|
| 373 | | # to cherrypy.session to a thread-specific dictionary called |
|---|
| 374 | | # cherrypy.request._session.data |
|---|
| 375 | | class SessionWrapper: |
|---|
| 376 | | |
|---|
| 377 | | def __getattr__(self, name): |
|---|
| 378 | | sess = getattr(cherrypy.request, "_session", None) |
|---|
| 379 | | if sess is None: |
|---|
| 380 | | raise SessionNotEnabledError() |
|---|
| 381 | | |
|---|
| 382 | | # Create thread-specific dictionary if needed |
|---|
| 383 | | if name == 'acquire_lock': |
|---|
| 384 | | return sess.storage.acquire_lock |
|---|
| 385 | | elif name == 'release_lock': |
|---|
| 386 | | return sess.storage.release_lock |
|---|
| 387 | | elif name == 'id': |
|---|
| 388 | | return sess.id |
|---|
| 389 | | |
|---|
| 390 | | if not sess.loaded: |
|---|
| 391 | | data = sess.storage.load(sess.id) |
|---|
| 392 | | # data is either None or a tuple (session_data, expiration_time) |
|---|
| 393 | | if data is None or data[1] < datetime.datetime.now(): |
|---|
| 394 | | # Expired session: |
|---|
| 395 | | # flush session data (but keep the same id) |
|---|
| 396 | | sess.data = {'_id': sess.id} |
|---|
| 397 | | if not (data is None): |
|---|
| 398 | | sess.on_renew(sess.data) |
|---|
| 399 | | else: |
|---|
| 400 | | sess.data = data[0] |
|---|
| 401 | | sess.loaded = True |
|---|
| 402 | | |
|---|
| 403 | | return getattr(sess.data, name) |
|---|
| 404 | | |
|---|
| 405 | | |
|---|
| 406 | | # The actual hook functions |
|---|
| | 307 | |
|---|
| | 308 | def clean_up(self): |
|---|
| | 309 | """Clean up expired sessions.""" |
|---|
| | 310 | self.cursor.execute('delete from session where expiration_time < %s', |
|---|
| | 311 | (datetime.datetime.now(),)) |
|---|
| | 312 | |
|---|
| | 313 | |
|---|
| | 314 | # Hook functions (for CherryPy tools) |
|---|
| 430 | | sess.storage.release_lock() |
|---|
| 431 | | if sess.storage: |
|---|
| 432 | | sess.storage = None |
|---|
| 433 | | |
|---|
| | 339 | sess.release_lock() |
|---|
| | 340 | |
|---|
| | 341 | def init(storage_type='ram', path=None, path_header=None, name='session_id', |
|---|
| | 342 | timeout=60, domain=None, secure=False, locking='implicit', |
|---|
| | 343 | clean_freq=5, **kwargs): |
|---|
| | 344 | """Initialize session object (using cookies). |
|---|
| | 345 | |
|---|
| | 346 | Any additional kwargs will be bound to the new Session instance. |
|---|
| | 347 | """ |
|---|
| | 348 | |
|---|
| | 349 | request = cherrypy.request |
|---|
| | 350 | |
|---|
| | 351 | # Check if request came with a session ID |
|---|
| | 352 | id = None |
|---|
| | 353 | if name in request.simple_cookie: |
|---|
| | 354 | id = request.simple_cookie[name].value |
|---|
| | 355 | |
|---|
| | 356 | if not hasattr(cherrypy, "session"): |
|---|
| | 357 | cherrypy.session = cherrypy._ThreadLocalProxy('session') |
|---|
| | 358 | |
|---|
| | 359 | # Create and attach a new Session instance to cherrypy.request. |
|---|
| | 360 | # It will possess a reference to (and lock, and lazily load) |
|---|
| | 361 | # the requested session data. |
|---|
| | 362 | storage_class = storage_type.title() + 'Session' |
|---|
| | 363 | cherrypy.serving.session = sess = globals()[storage_class](id) |
|---|
| | 364 | sess.timeout = timeout |
|---|
| | 365 | sess.clean_freq = clean_freq |
|---|
| | 366 | for k, v in kwargs.iteritems(): |
|---|
| | 367 | setattr(sess, k, v) |
|---|
| | 368 | |
|---|
| | 369 | if locking == 'implicit': |
|---|
| | 370 | sess.acquire_lock() |
|---|
| | 371 | |
|---|
| | 372 | # Set response cookie |
|---|
| | 373 | cookie = cherrypy.response.simple_cookie |
|---|
| | 374 | cookie[name] = sess.id |
|---|
| | 375 | cookie[name]['path'] = path or request.headers.get(path_header) or '/' |
|---|
| | 376 | |
|---|
| | 377 | # We'd like to use the "max-age" param as indicated in |
|---|
| | 378 | # http://www.faqs.org/rfcs/rfc2109.html but IE doesn't |
|---|
| | 379 | # save it to disk and the session is lost if people close |
|---|
| | 380 | # the browser. So we have to use the old "expires" ... sigh ... |
|---|
| | 381 | ## cookie[name]['max-age'] = timeout * 60 |
|---|
| | 382 | if timeout: |
|---|
| | 383 | cookie[name]['expires'] = http.HTTPDate(time.time() + (timeout * 60)) |
|---|
| | 384 | if domain is not None: |
|---|
| | 385 | cookie[name]['domain'] = domain |
|---|
| | 386 | if secure: |
|---|
| | 387 | cookie[name]['secure'] = 1 |
|---|
| | 388 | |
|---|