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

 


posted @ 2012-06-16 02:07  横竖弯钩  阅读(2618)  评论(0编辑  收藏  举报