-
-
Save nick-jiang/ecb6ec18b3fdc6c8ddf8 to your computer and use it in GitHub Desktop.
Redis based simple session store
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# _*_ encoding: utf-8 _*_ | |
import time | |
from tornado.web import RequestHandler | |
from utils.session import RedisSessionStore | |
SESSION_COOKIE_NAME = 'session_id' | |
SESSION_COOKIE_DOMAIN = '127.0.0.1' | |
SESSION_COOKIE_PATH = '/' | |
SESSION_SAVE_EVERY_REQUEST = True | |
class WebRequestHandler(RequestHandler): | |
def __init__(self, application, request, **kwargs): | |
self.session_store = RedisSessionStore | |
super(WebRequestHandler, self).__init__(application, request, **kwargs) | |
def prepare(self): | |
""" | |
Called at the beginning of a request before get/post etc. | |
""" | |
session_key = self.get_cookie(SESSION_COOKIE_NAME) | |
self.session = self.session_store(session_key) | |
def on_finish(self): | |
""" | |
Called after end of a request. | |
Note: it is called after the response has been sent to the client. | |
""" | |
pass | |
def save_cookie(self): | |
""" | |
Save cookie to redis database and user client. | |
Note: after modify session, don't forget to call save_cookie(). | |
""" | |
# This is for case of client side cookie expired but not deleted(vary based on browser behaviour). | |
if SESSION_COOKIE_NAME in self.request.cookies and self.session.is_empty(): | |
self.clear_cookie( | |
SESSION_COOKIE_NAME, | |
domain=SESSION_COOKIE_DOMAIN, | |
path=SESSION_COOKIE_PATH | |
) | |
else: | |
if self.session.modified or SESSION_SAVE_EVERY_REQUEST: | |
self.session.save() | |
max_age = self.session.get_expiry_age() | |
expires_time = time.time() + max_age | |
self.set_cookie( | |
SESSION_COOKIE_NAME, | |
self.session.session_key, | |
expires=expires_time, | |
domain=SESSION_COOKIE_DOMAIN, | |
path=SESSION_COOKIE_PATH | |
) | |
def login(self, user): | |
""" | |
Will store user specific data to session store. | |
""" | |
self.session['uid'] = user['id'] | |
self.save_cookie() | |
def logout(self): | |
""" | |
Delete user specific session data. | |
""" | |
self.session.flush() | |
def authenticate(self, **credentials): | |
""" | |
This should be implemented as needed. | |
When user is authenticated, then call log in for the user. | |
Don't forget to call save_cookie to store session data to | |
redis and set_cookie for client. | |
""" | |
raise NotImplementedError('authenticae() method must be implemented.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# _*_ encoding: utf-8 _*_ | |
import time | |
import random | |
import string | |
try: | |
import cPickle as pickle | |
except ImportError: | |
import pickle | |
import redis | |
VALID_KEY_CHARS = string.ascii_lowercase + string.digits | |
DEFAULT_SESSION_COOKIE_EXPIRY = 60 * 60 * 24 * 7 | |
class SessionBase(object): | |
def __init__(self, session_key): | |
self._session_key = session_key | |
self.modified = False | |
def __getitem__(self, key): | |
return self._sessiondata[key] | |
def __setitem__(self, key, value): | |
self._sessiondata[key] = value | |
self.modified = True | |
def __delitem__(self, key): | |
del self._sessiondata[key] | |
self.modified = True | |
def __len__(self): | |
return len(self._sessiondata) | |
def __contains__(self, key): | |
return key in self._sessiondata | |
def get(self, key, default=None): | |
return self._sessiondata.get(key, default) | |
@property | |
def session_key(self): | |
return self._session_key | |
def _get_new_session_key(self): | |
""" | |
There exists more secure way. | |
See django/utils/crypto.py get_random_string() method for more information. | |
""" | |
while True: | |
session_key = ''.join(random.choice(VALID_KEY_CHARS) for i in range(32)) | |
if not self.exists(session_key): | |
break | |
return session_key | |
def get_or_create_session_key(self): | |
if self._session_key is None: | |
self._session_key = self._get_new_session_key() | |
return self._session_key | |
def get_sessiondata(self): | |
""" | |
Lazily loads sessiond data from storage. | |
""" | |
try: | |
return self._sessiondata_cache | |
except AttributeError: | |
self._sessiondata_cache = {} if not self.session_key else self.load() | |
return self._sessiondata_cache | |
_sessiondata = property(get_sessiondata) | |
def clear(self): | |
self._sessiondata_cache = {} | |
self.modified = True | |
def flush(self): | |
""" | |
Delete server side related session data. | |
""" | |
self.clear() | |
self.delete() | |
self._session_key = None | |
def is_empty(self): | |
try: | |
return not self._session_key and not self._sessiondata_cache | |
except AttributeError: | |
return True | |
def get_expiry_age(self, **kwargs): | |
try: | |
expiry = kwargs['expiry'] | |
except KeyError: | |
expiry = DEFAULT_SESSION_COOKIE_EXPIRY | |
return expiry | |
# Following methods must be provided by subclasses | |
def exists(self, session_key): | |
raise NotImplementedError('subclasses of SessionBase must implement exists() method') | |
def load(self): | |
""" | |
Load the session data. | |
""" | |
raise NotImplementedError('subclasses of SessionBase must implement load() method') | |
def save(self): | |
""" | |
Save the session data. | |
""" | |
raise NotImplementedError('subclasses of SessionBase must implement save() method') | |
def delete(self): | |
""" | |
Delete the session data. | |
""" | |
raise NotImplementedError('subclasses of SessionBase must implement delete() method') | |
class RedisSessionStore(SessionBase): | |
def __init__(self, session_key, **options): | |
self.options = { | |
'hash_key_prefix': 'session', | |
'key': 'data', | |
'expiry': 60 * 60 * 24, | |
} | |
self.options.update(options) | |
self.redis = redis.StrictRedis() | |
super(RedisSessionStore, self).__init__(session_key, **options) | |
@property | |
def cache_key(self): | |
return self.options['hash_key_prefix'] + self.get_or_create_session_key() | |
def exists(self, session_key): | |
return self.redis.exists(session_key) | |
def load(self): | |
session_data = self.redis.hget(self.cache_key, self.options['key']) | |
if session_data: | |
return pickle.loads(session_data) | |
return {} | |
def save(self): | |
self.redis.hset(self.cache_key, self.options['key'], pickle.dumps(self.get_sessiondata())) | |
self.redis.expire(self.cache_key, self.get_expiry_age(**self.options)) | |
def delete(self): | |
self.redis.delete(self.cache_key) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment