web.py之session存储方式分析
由于HTTP是无状态的协议,所以有了cookie,所以有了session。
当用户与服务器连接时,服务器给每个用户一个session,并设定其中内容。
session是建立在cookie之上的。当一个session第一次被启用时,一个唯一的标识被存储于本地的cookie中。cookie保存在本地,session保存在服务器。
事情似乎很简单,使用字典即可,保存在cookie的id作为key,内容作为value。使用python的字典数据结构即可。的确这是其中一种解决方案。
web.py的session存储方案是很灵活的,web.session.Store是session对象存储的基类,我们可以的继承这个类来实现不同的存储方案:
class Store: """Base class for session stores""" def __contains__(self, key): raise NotImplementedError def __getitem__(self, key): raise NotImplementedError def __setitem__(self, key, value): raise NotImplementedError def cleanup(self, timeout): """removes all the expired sessions""" raise NotImplementedError def encode(self, session_dict): """encodes session dict as a string""" pickled = pickle.dumps(session_dict) return base64.encodestring(pickled) def decode(self, session_data): """decodes the data to get back the session dict """ pickled = base64.decodestring(session_data) return pickle.loads(pickled)
web.py提供了两种不同方案的实现:磁盘储存和数据库存储。
磁盘储存方案:
class DiskStore(Store): def __init__(self, root): # if the storage root doesn't exists, create it. if not os.path.exists(root): os.makedirs( os.path.abspath(root) ) self.root = root def _get_path(self, key): if os.path.sep in key: raise ValueError, "Bad key: %s" % repr(key) return os.path.join(self.root, key) def __contains__(self, key): path = self._get_path(key) return os.path.exists(path) def __getitem__(self, key): path = self._get_path(key) if os.path.exists(path): pickled = open(path).read() return self.decode(pickled) else: raise KeyError, key def __setitem__(self, key, value): path = self._get_path(key) pickled = self.encode(value) try: f = open(path, 'w') try: f.write(pickled) finally: f.close() except IOError: pass def __delitem__(self, key): path = self._get_path(key) if os.path.exists(path): os.remove(path) def cleanup(self, timeout): now = time.time() for f in os.listdir(self.root): path = self._get_path(f) atime = os.stat(path).st_atime if now - atime > timeout : os.remove(path)
数据库存储方案。需要先在数据库中建立其指定列的表:
session_id CHAR(128) UNIQUE NOT NULL,
atime DATETIME NOT NULL default current_timestamp,
data TEXT
实现代码如下:class DBStore(Store): """Store for saving a session in database Needs a table with the following columns: session_id CHAR(128) UNIQUE NOT NULL, atime DATETIME NOT NULL default current_timestamp, data TEXT """ def __init__(self, db, table_name): self.db = db self.table = table_name def __contains__(self, key): data = self.db.select(self.table, where="session_id=$key", vars=locals()) return bool(list(data)) def __getitem__(self, key): now = datetime.datetime.now() try: s = self.db.select(self.table, where="session_id=$key", vars=locals())[0] self.db.update(self.table, where="session_id=$key", atime=now, vars=locals()) except IndexError: raise KeyError else: return self.decode(s.data) def __setitem__(self, key, value): pickled = self.encode(value) now = datetime.datetime.now() if key in self: self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals()) else: self.db.insert(self.table, False, session_id=key, data=pickled ) def __delitem__(self, key): self.db.delete(self.table, where="session_id=$key", vars=locals()) def cleanup(self, timeout): timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg last_allowed_time = datetime.datetime.now() - timeout self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
除了上面两种实现方案外,web.py还提供了使用shelf实现的方案,但其实也是文件存储方案。
对于上面几种内置实现方案,我们都有个疑惑,文件和数据库操作都是比较慢的,为何不直接使用内存呢?对于分布式系统,直接使用文件或内存是无法完成会话管理的任务的。所以处理数据库方式外,我们可以使用memcache。
最近使用sae做点小应用,就碰到了这个问题了,不能使用本地文件,不想用数据库。所以使用重载Store实现了一个使用memcache实现的session存储方案,另外,使用sae的kvdb也是可以的,不需要修改代码。
代码如下:
class MemStore(Store): def __init__(self, memcache): self.mc = memcache def __contains__(self, key): data = self.mc.get(key) return bool(data) def __getitem__(self, key): now = time.time() value = self.mc.get(key) if not value: raise KeyError else: value['attime'] = now self.mc.set(key,value) return value def __setitem__(self, key, value): now = time.time() value['attime'] = now s = self.mc.get(key) self.mc.set(key, value, web.config.session_parameters['timeout']) def __delitem__(self, key): self.mc.delete(key) def cleanup(self, timeout): pass