-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathSessionRedisStore.py
More file actions
233 lines (192 loc) · 7.57 KB
/
Copy pathSessionRedisStore.py
File metadata and controls
233 lines (192 loc) · 7.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""Session store using the Redis in-memory data store."""
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
try:
import redis
except Exception:
raise ImportError("For using Redis sessions, redis-py must be installed.")
from MiscUtils import NoDefault
from SessionStore import SessionStore
debug = False
class SessionRedisStore(SessionStore):
"""A session store using Redis.
Stores the sessions in a single Redis store using 'last write wins'
semantics. This increases fault tolerance and allows server clustering.
In clustering configurations with concurrent writes for the same
session(s) the last writer will always overwrite the session.
The keys are prefixed with a configurable namespace, allowing you to
store other data in the same Redis system.
Cleaning/timing out of sessions is performed by Redis itself
since no one app server can know about the existence of all sessions or
the last access for a given session. Besides it is built in Redis
functionality. Consequently, correct sizing of Redis is necessary
to hold all user's session data.
You need to install the redis client to be able to use this module:
https://pypi.python.org/pypi/redis
You also need a Redis server: http://redis.io/
Contributed by Christoph Zwerschke, August 2016.
"""
## Init ##
def __init__(self, app):
SessionStore.__init__(self, app)
# the list of redis servers
self._host = app.setting('RedisHost', 'localhost')
self._port = app.setting('RedisPort', 6379)
self._db = app.setting('RedisDb', 0)
self._password = app.setting('RedisPassword', None)
# timeout in seconds
self._sessionTimeout = app.setting(
'SessionTimeout', 180) * 60
# the redis "namespace" used by our store
self._namespace = app.setting(
'RedisNamespace', 'WebwareSession:') or ''
self._redis = redis.StrictRedis(self._host,
self._port, self._db, self._password)
## Access ##
def __len__(self):
"""Return the number of sessions in the store."""
if debug:
print ">> len()"
return len(self.keys())
def __getitem__(self, key):
"""Get a session item, reading it from the store."""
if debug:
print ">> getitem(%s)" % key
# returns None if key non-existent or no server to contact
try:
value = loads(self._redis.get(self.redisKey(key)))
except Exception:
value = None
if value is None:
# SessionStore expects KeyError when no result
raise KeyError(key)
return value
def __setitem__(self, key, value):
"""Set a session item, writing it to the store."""
if debug:
print ">> setitem(%s, %s)" % (key, value)
dirty = value.isDirty()
if self._alwaysSave or dirty:
if dirty:
value.setDirty(False)
try:
self._redis.setex(self.redisKey(key), self._sessionTimeout,
dumps(value, -1))
except Exception as exc:
if dirty:
value.setDirty()
# Not able to store the session is a failure
print "Error saving session '%s' to redis: %s" % (key, exc)
self.application().handleException()
def __delitem__(self, key):
"""Delete a session item from the store.
Note that in contracts with SessionFileStore,
not finding a key to delete isn't a KeyError.
"""
if debug:
print ">> delitem(%s)" % key
session = self[key]
if not session.isExpired():
session.expiring()
try:
self._redis.delete(self.redisKey(key))
except Exception as exc:
# Not able to delete the session is a failure
print "Error deleting session '%s' from redis: %s" % (key, exc)
self.application().handleException()
def __contains__(self, key):
"""Check whether the session store has a given key."""
if debug:
print ">> contains(%s)" % key
try:
return self._redis.exists(self.redisKey(key))
except Exception as exc:
# Not able to check the session is a failure
print "Error checking session '%s' from redis: %s" % (key, exc)
self.application().handleException()
def __iter__(self):
"""Return an iterator over the stored session keys."""
if debug:
print ">> iter()"
return self.keys().__iter__()
def keys(self):
"""Return a list with the keys of all the stored sessions."""
if debug:
print ">> keys()"
try:
if self._namespace:
n = len(self._namespace)
return [k[n:] for k in self._redis.keys(self.redisKey('*'))]
else:
return self._redis.keys(self.redisKey('*'))
except Exception as exc:
# Not able to get the keys is a failure
print "Error checking sessions from redis: %s" % (exc,)
self.application().handleException()
def clear(self):
"""Clear the session store, removing all of its items."""
if debug:
print ">> clear()"
try:
if self._namespace:
self._redis.delete(*self._redis.keys(self.redisKey('*')))
else:
self._redis.flushdb()
except Exception as exc:
# Not able to clear the store is a failure
print "Error clearing sessions from redis: %s" % (exc,)
self.application().handleException()
def setdefault(self, key, default=None):
"""Return value if key available, else default (also setting it)."""
if debug:
print ">> setdefault(%s, %s)" % (key, default)
try:
return self[key]
except KeyError:
self[key] = default
return default
def pop(self, key, default=NoDefault):
"""Return value if key available, else default (also remove key)."""
if debug:
print ">> pop(%s, %s)" % (key, default)
if default is NoDefault:
value = self[key]
del self[key]
return value
else:
try:
value = self[key]
except KeyError:
return default
else:
del self[key]
return value
## Application support ##
def storeSession(self, session):
"""Save potentially changed session in the store."""
if debug:
print ">> storeSession(%s)" % session
self[session.identifier()] = session
def storeAllSessions(self):
"""Permanently save all sessions in the store.
Should be used (only) when the application server is shut down.
This closes the connections to the Redis server.
"""
if debug:
print ">> storeAllSessions()"
try:
self._redis.connection_pool.disconnect()
except Exception as exc:
print "Not able to disconnect from redis: %s" % (exc,)
def cleanStaleSessions(self, task=None):
"""Clean stale sessions.
Redis does this on its own, so we do nothing here.
"""
if debug:
print ">> cleanStaleSessions()"
## Auxiliary methods ##
def redisKey(self, key):
"""Create the real key with namespace to be used with Redis."""
return self._namespace + key