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
264
265 - def _load(self, path=None):
266 if path is None:
267 path = self._get_file_path()
268 try:
269 f = open(path, "rb")
270 try:
271 return pickle.load(f)
272 finally:
273 f.close()
274 except (IOError, EOFError):
275 return None
276
277 - def _save(self, expiration_time):
278 f = open(self._get_file_path(), "wb")
279 try:
280 pickle.dump((self._data, expiration_time), f)
281 finally:
282 f.close()
283
285 try:
286 os.unlink(self._get_file_path())
287 except OSError:
288 pass
289
291 if path is None:
292 path = self._get_file_path()
293 path += self.LOCK_SUFFIX
294 while True:
295 try:
296 lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
297 except OSError:
298 time.sleep(0.1)
299 else:
300 os.close(lockfd)
301 break
302 self.locked = True
303
309
311 """Clean up expired sessions."""
312 now = datetime.datetime.now()
313
314 for fname in os.listdir(self.storage_path):
315 if (fname.startswith(self.SESSION_PREFIX)
316 and not fname.endswith(self.LOCK_SUFFIX)):
317
318
319 path = os.path.join(self.storage_path, fname)
320 self.acquire_lock(path)
321 try:
322 contents = self._load(path)
323
324 if contents is not None:
325 data, expiration_time = contents
326 if expiration_time < now:
327
328 os.unlink(path)
329 finally: