Skip to content

Commit

Permalink
[web]: Improve the API for session management and add expire support.
Browse files Browse the repository at this point in the history
Fixes #133
  • Loading branch information
prologic committed Jan 23, 2017
1 parent 70080ac commit 5772548
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 11 deletions.
85 changes: 75 additions & 10 deletions circuits/web/sessions.py
Expand Up @@ -3,6 +3,7 @@
This module implements Session Components that can be used to store
and access persistent information.
"""
from abc import ABCMeta, abstractmethod
from collections import defaultdict
from hashlib import sha1 as sha
from uuid import uuid4 as uuid
Expand Down Expand Up @@ -48,30 +49,94 @@ def verify_session(request, sid):
return sid


class Session(dict):

def __init__(self, sid, data, store):
super(Session, self).__init__(data)

self._sid = sid
self._store = store

@property
def sid(self):
return self._sid

@property
def store(self):
return self._store

def expire(self):
self.store.delete(self.sid)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
self.store.save(self.sid, self)


class Store(object):

__metaclass__ = ABCMeta

@abstractmethod
def delete(self, sid):
"""Delete the session data identified by sid"""

@abstractmethod
def load(self, sid):
"""Load the session data identified by sid"""

@abstractmethod
def save(self, sid):
"""Save the session data identified by sid"""


class MemoryStore(Store):

def __init__(self):
self._data = defaultdict(dict)

@property
def data(self):
return self._data

def delete(self, sid):
del self.data[sid]

def load(self, sid):
return Session(sid, self.data[sid], self)

def save(self, sid, data):
self.data[sid] = data


class Sessions(Component):

channel = "web"

def __init__(self, name="circuits.session", channel=channel):
def __init__(self, name="circuits", store=MemoryStore, channel=channel):
super(Sessions, self).__init__(channel=channel)

self._name = name
self._data = defaultdict(dict)
self._store = store()

def load(self, sid):
return self._data[sid]
@property
def name(self):
return self._name

def save(self, sid, data):
"""Save User Session Data for sid"""
@property
def store(self):
return self._store

@handler("request", priority=10)
def request(self, request, response):
if self._name in request.cookie:
if self.name in request.cookie:
sid = request.cookie[self._name].value
sid = verify_session(request, sid)
else:
sid = create_session(request)

request.sid = sid
request.session = self.load(sid)
response.cookie[self._name] = sid
request.session = self.store.load(sid)
response.cookie[self.name] = sid
34 changes: 33 additions & 1 deletion tests/web/test_sessions.py
Expand Up @@ -9,12 +9,17 @@ class Root(Controller):
def index(self, vpath=None):
if vpath:
name = vpath
self.session["name"] = name
with self.session as data:
data["name"] = name
else:
name = self.session.get("name", "World!")

return "Hello %s" % name

def logout(self):
self.session.expire()
return "OK"


def test(webapp):
Sessions().register(webapp)
Expand All @@ -33,3 +38,30 @@ def test(webapp):
f = opener.open(webapp.server.http.base)
s = f.read()
assert s == b"Hello test"


def test_expire(webapp):
Sessions().register(webapp)

cj = CookieJar()
opener = build_opener(HTTPCookieProcessor(cj))

f = opener.open(webapp.server.http.base)
s = f.read()
assert s == b"Hello World!"

f = opener.open(webapp.server.http.base + "/test")
s = f.read()
assert s == b"Hello test"

f = opener.open(webapp.server.http.base)
s = f.read()
assert s == b"Hello test"

f = opener.open(webapp.server.http.base + "/logout")
s = f.read()
assert s == b"OK"

f = opener.open(webapp.server.http.base)
s = f.read()
assert s == b"Hello World!"

0 comments on commit 5772548

Please sign in to comment.