Pages

Saturday, April 25, 2009

Session handling with Memcache API in Google AppEngine

I've been trying to teach myself Python by witting a basic Twitter client in Google AppEngine. 

One of the things which I found lacking from the Webapp and Django frameworks was anything to manage sessions well. So, realising that the Memcache API has a timeout I figured that I could use that as the basis for session management. 

I ended up creating 2 classes, a SessionManager class to create, retrieve and delete sessions ans a Session object which would act as a simple dictionary to store arbitrary session values in. 

I would then be able to use the Session and SessionManager classes like this in my Controller class: 
class Controller(object):
def __init__(self, request, response):
self.request = request
self.response = response
self.sessionManager = SessionManager(self.request, self.response)
self.session = self.sessionManager.current()
self.initialize()


The SessionManager constructor needs both Request and Response objects because it needs to read and write Cookies. 

Here's the SessionManager class: 
from google.appengine.api import memcache
import datetime
import random

class SessionManager(object):
def __init__(self, request, response, timeout=1200):
self.timeout = timeout
self.request = request
self.response = response
self.cookieName = 'SID'
def current(self):
cookievalue = self.request.cookies.get(self.cookieName)
if ((cookievalue is not None) and (memcache.get(cookievalue) is not None)):
return Session(cookievalue, self.timeout, False)
else:
return self.createSession()
def createSession(self):
newId = self.createNewId()
memcache.set(key=newId, value=True, time=self.timeout, )
now = datetime.datetime.now()
inc = datetime.timedelta(seconds=self.timeout)
now += inc
self.setCookie(key=self.cookieName,value=newId,expires=now)
return Session(newId, self.timeout, True)

def destroySession(self):
self.clearCookie(self.cookieName)
def createNewId(self):
newHash = str(hash(datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S%f'))) + str(random.random())
while memcache.get(newHash) is not None:
newHash = self.CreateNewId()
return newHash
def setCookie(self,key,value,expires,path='/'):
self.response.headers.add_header('Set-Cookie', key+'='+value+ ' path='+path+'; expires '+expires.strftime('%a, %d-%b-%Y %H:%M:00 %Z'))
def clearCookie(self,key):
self.setCookie(key=key,value='',expires=datetime.datetime.now())


I'm creating a unique sessionId by concatenating the current date/time with a random number. 

Here's the Session class: 
class Session(object):
def __init__(self, id, timeout, isNew=False):
self.id = id
self.IsNew = isNew
self.keys = dict()
self.timeout = timeout
memcache.set(key=self.id+'__keys', value=self.keys, time=timeout)
def __getitem__(self, key):
return memcache.get(self.id+'_'+key)
def __setitem__(self,key,value):
memcache.set(key=self.id+'_'+key, value=value, time=self.timeout)
self.keys[key] = value
memcache.set(key=self.id+'__keys', value=self.keys, time=self.timeout)
def hasKey(self, key):
self.keys = memcache.get(self.id+'__keys')
return (key in self.keys)


That's kind of it really. It's working well at the moment but I've not used it with very large numbers of concurrent users.

No comments: