Derek's Redis Session Middleware
I started a bottle project with the beaker session example everyone seems to give:
# EXAMPLE OF BEAKER'S MIDDLEWARE - DO NOT USE!!!
import bottle
from beaker.middleware import SessionMiddleware
session_opts = {
'session.type': 'ext:memcached',
'session.url': '127.0.0.1:11211',
'session.cookie_expires': False,
'session.auto': True,
'session.key': 'hvst-session',
'session.lock_dir': '/tmp/.lock_dir',
}
app = SessionMiddleware(bottle.app(), session_opts)
@bottle.route('/test')
def test():
s = bottle.request.environ.get('beaker.session')
s['test'] = s.get('test',0) + 1
s.save()
return 'Test counter: %d' % s['test']
bottle.run(app=app)
But it didn't quite fit our needs for the following reasons:
memcached
which drops old sessions after a while (and all sessions during restarts).Beaker's session middleware supposedly supports Redis (which in theory would solve the first problem, but we couldn't get it to work), but the rest remain. So I wrote this bit of code.
$ sudo pip install drsession
import bottle, drsession
app = drsession.SessionMiddleware(bottle.app())
@bottle.hook('before_request')
def setup_session():
bottle.request.session = bottle.request.environ.get('drsession')
@bottle.route('/set')
def set():
bottle.request.session['foo'] = 'bar'
return 'OK'
@bottle.route('/get')
def get():
return bottle.request.session['foo']
bottle.run(app=app)
This backs your session object with the Redis server instance running on localhost
(using a connection pool) and sets request.session
as the dictionary-like interface.
There is no request.session.save()
.
If I call request.session['foo'] = 'bar'
, it sets it in Redis right then and there. If Redis is unavailable or throws an exception, you get that exception. No more "I JUST set that - where the heck did it go?" debugging.
If I call request.session['foo'] = 'bar'
, just write foo
.
Don't write every value in my entire session every time I set anything.
Definitely don't write every value in my entire session every time I read anything.
Your session id (plus a prefix) is your Redis key. The keys in your session are keys to the Redis hash type set at your Redis key. The hash values are json
encoded strings of the objects in your session.
HINCRBY
for +=
OperationsFor fast, race-free counters.
By default DRSession serializes with the python json
module. If you wish for a different encoding, pass in new functions to the loads
and dumps
parameters of SessionMiddleware
.
This isn't rocket science. One file (drsession.py
) is required to use. Very few lines of code.
>>> help(drsession.SessionMiddleware)
class SessionMiddleware(__builtin__.object)
| Methods defined here:
| __init__(self, app,
| prefix='drsession:', cookie='drsession', env='drsession',
| redis_server=None, redis_kwargs={},
| connection_pool=None, connection_pool_kwargs={},
| loads=None, dumps=None)
Option | Description |
---|---|
app |
Any WSGI app object (ie any callable object that takes environ, start_response, exec_info=None ). |
prefix |
The Redis key prefix. Default is 'drsession:' . If your session id is abc123 your Redis key will be 'drsession:abc123' . |
cookie |
The HTML cookie storing your session id. |
env |
The key used to store the session object in bottle.request.environ . |
redis_server and redis_kwargs
|
If redis_server is provided it is used directly. Else created with redis.Redis(**redis_kwargs)
|
connection_pool and connection_pool_kwargs
|
If connection_pool is provided it is used directly. Else created with redis.ConnectionPool(**connection_pool_kwargs) if connection_pool_kwargs is defined. To not use any connection pooling set connection_pool_kwargs=None . To be "used" means to be added to redis_kwargs . |
dumps and loads
|
Serialization and deserialization functions. Default to json.dumps and json.loads . |
$ python test.py
..................
----------------------------------------------------------------------
Ran 18 tests in 0.010s
OK
$ cat /proc/cpuinfo | grep "model name" | uniq
model name : Intel(R) Core(TM) i5-6260U CPU @ 1.80GHz
$ python performance.py
--==[ DRSession ]==--
Hitting a local Redis server.
All data changes are live.
TESTING: session['foo'] = 'bar' # set a value
20000 loops took 0.862565040588 seconds (0.043ms per loop)
TESTING: session['foo'] # read a value
20000 loops took 0.903891086578 seconds (0.045ms per loop)
TESTING: 'foo' in session # does a value exist?
20000 loops took 0.729822874069 seconds (0.036ms per loop)
TESTING: del session['foo'] # remove a value
20000 loops took 0.743810892105 seconds (0.037ms per loop)
TESTING: session.save() # doesn't do anything
20000 loops took 0.00244402885437 seconds (0.000ms per loop)