1 """Session implementation for CherryPy.
2
3 We use cherrypy.request to store some convenient variables as
4 well as data about the session for the current request. Instead of
5 polluting cherrypy.request we use a Session object bound to
6 cherrypy.session to store these variables.
7 """
8
9 import datetime
10 import os
11 try:
12 import cPickle as pickle
13 except ImportError:
14 import pickle
15 import random
16 import sha
17 import time
18 import threading
19 import types
20 from warnings import warn
21
22 import cherrypy
23 from cherrypy.lib import http
24
25
27
29 while True:
30 self.finished.wait(self.interval)
31 if self.finished.isSet():
32 return
33 self.function(*self.args, **self.kwargs)
34
35
36 missing = object()
37
39 """A CherryPy dict-like Session object (one per request)."""
40
41 __metaclass__ = cherrypy._AttributeDocstrings
42
43 id = None
44 id__doc = "The current session ID."
45
46 timeout = 60
47 timeout__doc = "Number of minutes after which to delete session data."
48
49 locked = False
50 locked__doc = """
51 If True, this session instance has exclusive read/write access
52 to session data."""
53
54 loaded = False
55 loaded__doc = """
56 If True, data has been retrieved from storage. This should happen
57 automatically on the first attempt to access session data."""
58
59 clean_thread = None
60 clean_thread__doc = "Class-level PerpetualTimer which calls self.clean_up."
61
62 clean_freq = 5
63 clean_freq__doc = "The poll rate for expired session cleanup in minutes."
64
66 self._data = {}
67
68 for k, v in kwargs.iteritems():
69 setattr(self, k, v)
70
71 self.id = id
72 while self.id is None:
73 self.id = self.generate_id()
74
75 if self._load() is not None:
76 self.id = None
77
84 clean_interrupt = classmethod(clean_interrupt)
85
87 """Clean up expired sessions."""
88 pass
89
90 try:
91 os.urandom(20)
92 except (AttributeError, NotImplementedError):
93
95 """Return a new session id."""
96 return sha.new('%s' % random.random()).hexdigest()
97 else:
99 """Return a new session id."""
100 return os.urandom(20).encode('hex')
101
103 """Save session data."""
104 try:
105
106
107 if self.loaded:
108 t = datetime.timedelta(seconds = self.timeout * 60)
109 expiration_time = datetime.datetime.now() + t
110 self._save(expiration_time)
111
112 finally:
113 if self.locked:
114
115 self.release_lock()
116
139
141 """Delete stored session data."""
142 self._delete()
143
147
151
155
162
166
170
171 - def get(self, key, default=None):
174
178
182
186
190
194
198
199
201
202
203 cache = {}
204 locks = {}
205
207 """Clean up expired sessions."""
208 now = datetime.datetime.now()
209 for id, (data, expiration_time) in self.cache.items():
210 if expiration_time < now:
211 try:
212 del self.cache[id]
213 except KeyError:
214 pass
215 try:
216 del self.locks[id]
217 except KeyError:
218 pass
219
222
223 - def _save(self, expiration_time):
224 self.cache[self.id] = (self._data, expiration_time)
225
228
232
236
237
239 """ Implementation of the File backend for sessions
240
241 storage_path: the folder where session data will be saved. Each session
242 will be saved as pickle.dump(data, expiration_time) in its own file;
243 the filename will be self.SESSION_PREFIX + self.id.
244 """
245
246 SESSION_PREFIX = 'session-'
247 LOCK_SUFFIX = '.lock'
248
250
251 lockfiles = [fname for fname in os.listdir(self.storage_path)
252 if (fname.startswith(self.SESSION_PREFIX)
253 and fname.endswith(self.LOCK_SUFFIX))]
254 if lockfiles:
255 plural = ('', 's')[len(lockfiles) > 1]
256 warn("%s session lockfile%s found at startup. If you are "
257 "only running one process, then you may need to "
258 "manually delete the lockfiles found at %r."
259 % (len(lockfiles), plural,
260 os.path.abspath(self.storage_path)))
261
263 f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id)
264 if not os.path.normpath(f).startswith(self.storage_path):
265 raise cherrypy.HTTPError(400, "Invalid session id in cookie.")
266 return f
267
268 - def _load(self, path=None):
269 if path is None:
270 path = self._get_file_path()
271 try:
272 f = open(path, "rb")
273 try:
274 return pickle.load(f)
275 finally:
276 f.close()
277 except (IOError, EOFError):
278 return None
279
280 - def _save(self, expiration_time):
281 f = open(self._get_file_path(), "wb")
282 try:
283 pickle.dump((self._data, expiration_time), f)
284 finally:
285 f.close()
286
288 try:
289 os.unlink(self._get_file_path())
290 except OSError:
291 pass
292
294 if path is None:
295 path = self._get_file_path()
296 path += self.LOCK_SUFFIX
297 while True:
298 try:
299 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
300 except OSError:
301 time.sleep(0.1)
302 else:
303 os.close(lockfd)
304 break
305 self.locked = True
306
312
314 """Clean up expired sessions."""
315 now = datetime.datetime.now()
316
317 for fname in os.listdir(self.storage_path):
318 if (fname.startswith(self.SESSION_PREFIX)
319 and not fname.endswith(self.LOCK_SUFFIX)):
320
321
322 path = os.path.join(self.storage_path, fname)
323 self.acquire_lock(path)