Skip to content

Instantly share code, notes, and snippets.

@nick-jiang
Forked from ayang/app.py
Last active August 29, 2015 14:05
Show Gist options
  • Save nick-jiang/ecb6ec18b3fdc6c8ddf8 to your computer and use it in GitHub Desktop.
Save nick-jiang/ecb6ec18b3fdc6c8ddf8 to your computer and use it in GitHub Desktop.
Redis based simple session store
#!/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.')
#!/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