Implemented experimental JSON based sessions

This commit is contained in:
Armin Ronacher 2012-08-11 02:36:14 +01:00
parent d4415dd665
commit 4df3bf2058
5 changed files with 172 additions and 1 deletions

View file

@ -10,8 +10,12 @@
:license: BSD, see LICENSE for more details.
"""
import cPickle as pickle
from datetime import datetime
from werkzeug.contrib.securecookie import SecureCookie
from werkzeug.http import http_date, parse_date
from .helpers import json, _assert_have_json
from . import Markup
class SessionMixin(object):
@ -41,10 +45,74 @@ class SessionMixin(object):
modified = True
class TaggedJSONSerializer(object):
"""A customized JSON serializer that supports a few extra types that
we take for granted when serializing (tuples, markup objects, datetime).
"""
def dumps(self, value):
if __debug__:
_assert_have_json()
def _tag(value):
if isinstance(value, tuple):
return {'##t': [_tag(x) for x in value]}
elif callable(getattr(value, '__html__', None)):
return {'##m': unicode(value.__html__())}
elif isinstance(value, list):
return [_tag(x) for x in value]
elif isinstance(value, datetime):
return {'##d': http_date(value)}
elif isinstance(value, dict):
return dict((k, _tag(v)) for k, v in value.iteritems())
return value
return json.dumps(_tag(value), separators=(',', ':'))
def loads(self, value):
if __debug__:
_assert_have_json()
def object_hook(obj):
if len(obj) != 1:
return obj
the_key, the_value = obj.iteritems().next()
if the_key == '##t':
return tuple(the_value)
elif the_key == '##m':
return Markup(the_value)
elif the_key == '##d':
return parse_date(the_value)
return obj
return json.loads(value, object_hook=object_hook)
session_json_serializer = TaggedJSONSerializer()
class SecureCookieSession(SecureCookie, SessionMixin):
"""Expands the session with support for switching between permanent
and non-permanent sessions.
and non-permanent sessions and changes the default pickle based
serialization format to a tagged json one.
"""
serialization_method = session_json_serializer
class _UpgradeSerializer(object):
def dumps(self, value):
return session_json_serializer.dumps(value)
def loads(self, value):
try:
return session_json_serializer.loads(value)
except Exception:
return pickle.loads(value)
class UpgradeSecureCookieSession(SecureCookieSession):
"""This cookie sesion implementation tries json first but will also
support pickle based session. This exists mainly to upgrade existing
pickle based users transparently to json.
.. versionadded:: 0.10
"""
serialization_method = _UpgradeSerializer()
class NullSession(SecureCookieSession):
@ -203,3 +271,13 @@ class SecureCookieSessionInterface(SessionInterface):
session.save_cookie(response, app.session_cookie_name, path=path,
expires=expires, httponly=httponly,
secure=secure, domain=domain)
class UpgradeSecureCookieSessionInterface(SecureCookieSessionInterface):
"""This session interface works exactly like the regular one but uses
the :class:`UpgradeSecureCookieSession` classes to upgrade from pickle
sessions to JSON sessions.
.. versionadded:: 0.10
"""
session_class = UpgradeSecureCookieSession

View file

@ -13,6 +13,7 @@ from __future__ import with_statement
import re
import flask
import pickle
import unittest
from datetime import datetime
from threading import Thread
@ -297,6 +298,31 @@ class BasicFunctionalityTestCase(FlaskTestCase):
self.assert_equal(c.get('/').data, 'None')
self.assert_equal(c.get('/').data, '42')
def test_session_special_types(self):
app = flask.Flask(__name__)
app.secret_key = 'development-key'
app.testing = True
now = datetime.utcnow().replace(microsecond=0)
@app.after_request
def modify_session(response):
flask.session['m'] = flask.Markup('Hello!')
flask.session['dt'] = now
flask.session['t'] = (1, 2, 3)
return response
@app.route('/')
def dump_session_contents():
return pickle.dumps(dict(flask.session))
c = app.test_client()
c.get('/')
rv = pickle.loads(c.get('/').data)
self.assert_equal(rv['m'], flask.Markup('Hello!'))
self.assert_equal(type(rv['m']), flask.Markup)
self.assert_equal(rv['dt'], now)
self.assert_equal(rv['t'], (1, 2, 3))
def test_flashes(self):
app = flask.Flask(__name__)
app.secret_key = 'testkey'