If you need to store a lot of session data it makes sense to move the data from the cookie to the server. In that case you might want to use redis as the storage backend for the actual session data.
The following code implements a session backend using redis. It allows you to either pass in a redis client or will connect to the redis instance on localhost. All the keys are prefixed with a specified prefix which defaults to session:.
import pickle
from datetime import timedelta
from uuid import uuid4
from redis import Redis
from werkzeug.datastructures import CallbackDict
from flask.sessions import SessionInterface, SessionMixin
class RedisSession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None, new=False):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.sid = sid
self.new = new
self.modified = False
class RedisSessionInterface(SessionInterface):
serializer = pickle
session_class = RedisSession
def __init__(self, redis=None, prefix='session:'):
if redis is None:
redis = Redis()
self.redis = redis
self.prefix = prefix
def generate_sid(self):
return str(uuid4())
def get_redis_expiration_time(self, app, session):
if session.permanent:
return app.permanent_session_lifetime
return timedelta(days=1)
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self.generate_sid()
return self.session_class(sid=sid)
val = self.redis.get(self.prefix + sid)
if val is not None:
data = self.serializer.loads(val)
return self.session_class(data, sid=sid)
return self.session_class(sid=sid, new=True)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
if not session:
self.redis.delete(self.prefix + session.sid)
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain)
return
redis_exp = self.get_redis_expiration_time(app, session)
cookie_exp = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
self.redis.setex(self.prefix + session.sid, val,
int(redis_exp.total_seconds()))
response.set_cookie(app.session_cookie_name, session.sid,
expires=cookie_exp, httponly=True,
domain=domain)
Here is how to enable it:
app = Flask(__name__)
app.session_interface = RedisSessionInterface()
If you get an attribute error that total_seconds is missing it means you're using a version of Python older than 2.7. In this case you can use this function as a replacement for the total_seconds method:
def total_seconds(td):
return td.days * 60 * 60 * 24 + td.seconds
This snippet by Armin Ronacher can be used freely for anything you like. Consider it public domain.
Comments
what is session.permanent by linnchord gao on 2012-01-10 @ 08:07
the code:
if session.permanent: return app.permanent_session_lifetime
Where to config the [session.permanent]? I set the param [PERMANENT_SESSION_LIFETIME] in conf and not available, because the session.permanent is False.
Comment by Armin Ronacher on 2012-01-19 @ 13:30
It's set per session. Just execute this:
Right after login or something.
Comment by Gurteshwar on 2012-06-29 @ 11:34
Hi, There's an error in this snippet, self.redis.setex(self.prefix + session.sid, val,int(redis_exp.total_seconds())) is wrong. The redis SETEX command format is SETEX key seconds value. In the snippet it's SETEX key value seconds. This raises an exception. Please correct it for future readers. Thanks
Comment by Jonathan Drake on 2012-10-16 @ 22:00
@Gurteshwar I just tried the snippet as is as well as with your suggested fix. Your syntax caused an error.
About the implementation of redis and this snippet by Jorge Vazquez on 2013-04-02 @ 16:33
@Jonathan Drake and @Gurteshwar and future readers.
If you use
import redis redis.StrictRedis()
@Gurteshwar comment fixes the issue.
However, if you use
from redis import Redis redis = Redis()
What you have on this snippet is what you should use.
Per the code at https://github.com/andymccurdy/redis-py/blob/master/redis/client.py , the difference between both implementations
class StrictRedis(object): def setex(self, name, time, value): ....
class Redis(StrictRedis): """ Provides backwards compatibility with older versions of redis-py that changed arguments to some commands to be more Pythonic, sane, or by accident. """
def setex(self, name, value, time): ....