Tornado Sessions with Redis!
So, first let me say... I love Tornado. Love it. It's simple, it's easy, it's asynchronous and non-blocking! What's not to love?
For me, I particularly have a fondness for Redis and using it for reliable web session storage and access. Whether you love it or hate it, I could care less, but I personally like it and it suits my needs quite well for most projects.
So taking these two technologies and using the awesome power of Tornado, and the great "No-SQL" (p.s. "No-SQL" is a stupid term) Redis and merging the two sounded like a pretty great idea. There remains only one problem.... Tornado really doesn't have a great native session handler! So I said to myself... Go forth and build Mike! And that leads us here to our session.py file:
try: import cPickle as pickle except: import pickle from uuid import uuid4 import time class RedisSessionStore: def __init__(self, redis_connection, **options): self.options = { 'key_prefix': 'session', 'expire': 5184000, } self.options.update(options) self.redis = redis_connection def prefixed(self, sid): return '%s:%s' % (self.options['key_prefix'], sid) def generate_sid(self, ): return uuid4().get_hex() def get_session(self, sid, name): data = self.redis.hget(self.prefixed(sid), name) session = pickle.loads(data) if data else dict() return session def set_session(self, sid, session_data, name): expiry = self.options['expire'] self.redis.hset(self.prefixed(sid), name, pickle.dumps(session_data)) if expiry: self.redis.expire(self.prefixed(sid), expiry) def delete_session(self, sid): self.redis.delete(self.prefixed(sid)) class Session: def __init__(self, session_store, sessionid=None): self._store = session_store self._sessionid = sessionid if sessionid else self._store.generate_sid() self._sessiondata = self._store.get_session(self._sessionid, 'data') self.dirty = False def clear(self): self._store.delete_session(self._sessionid) def access(self, remote_ip): access_info = {'remote_ip':remote_ip, 'time':'%.6f' % time.time()} self._store.set_session( self._sessionid, 'last_access', pickle.dumps(access_info) ) def last_access(self): access_info = self._store.get_session(self._sessionid, 'last_access') return pickle.loads(access_info) @property def sessionid(self): return self._sessionid def __getitem__(self, key): return self._sessiondata[key] def __setitem__(self, key, value): self._sessiondata[key] = value self._dirty() def __delitem__(self, key): del self._sessiondata[key] self._dirty() def __len__(self): return len(self._sessiondata) def __contains__(self, key): return key in self._sessiondata def __iter__(self): for key in self._sessiondata: yield key def __repr__(self): return self._sessiondata.__repr__() def __del__(self): if self.dirty: self._save() def _dirty(self): self.dirty = True def _save(self): self._store.set_session(self._sessionid, self._sessiondata, 'data') self.dirty = False
This simple session.py file is handy dandy for inclusion into your tornado project! Let's take a look at what may be in your main app .py file:
from session import Session # Our base handler class BaseHandler(tornado.web.RequestHandler): def __init__(self,application, request,**kwargs): super(BaseHandler,self).__init__(application,request) def get_current_user(self): return self.session['user'] if self.session and 'user' in self.session else None @property def session(self): sessionid = self.get_secure_cookie(<< YOUR AUTH COOKIE NAME >>,None) if sessionid: return Session(self.application.session_store, sessionid) else: sess = Session(self.application.session_store, None) self.set_secure_cookie(<< YOUR AUTH COOKIE NAME >>,sess.sessionid) return sess
Now, this particular example only works with a local redis server... However, you can always pass new connection parameters in to connect to a remote store or cluster.
This is a quick draft with some future editing in mind, but should you find any errors, let me know and I'll make sure to update appropriately! Enjoy!