Tornado 是我非常喜欢的一个框架,但是它缺失了很多功能模块,比如说 Session,正因为它啥都没有,所以我就爱上它了,这样可以方便自己撸轮子。:D

# Session 原理

Session 是由于 HTTP 协议是无状态的,所以需要一种机制来保持客户端和服务器之间的会话。

HTTP 协议的流程是

Client -> Send Request -> Server -> Return Response

上述过程,是一次性的,也就是说当客户端发送请求直到服务器返回响应之后,客户端和服务器之间就再也没有任何联系了。于是一般人都会想到,在每次请求时带上一个特定的标识符,然后服务器端记录下来,每次根据特定的标志符就可以知道客户端的身份了。那么,这个特定的标志符就是 session id。这个就是大致上 session 的原理了。

一般情况下,session id 是储存在 cookie 里,并且通过 HTTP Header 传递给服务器,当然,也可以作为 HTTP Query parameters,只是这样个人觉得不太优雅,当然也有一些不安全的因素存在。

# 为 Tornado 添加 Session 知道了 Session 的原理后,就可以很方便的为 Tornado 添加 Session 机制了。
## SessionMixin 下面是一个 SessionMixin 类,作用是开启 Session 和保存 Session,和 PHP 的 `session_start` 简直是一样一样的作用,其中 `self.session_storage` 是服务器的 Session 储存容器。 ``` python class SessionMixin(object):
def open_session(self):
    self._session_name = self.get_secure_cookie('tornado-session')
    self.session = self.session_storage.get(self._session_name) or {}
    self._session_age = 3600 * 24 * 31

def set_session_age(self, expires):
    self._session_age = expires

def save_session(self):
    if not getattr(self, '_session_name'):
        self._session_name = str(uuid.uuid4())
        self.set_secure_cookie('wing2-session', self._session_name)
    self.session_storage.set(self._session_name, self.session, self._session_age)
<div id="with-tornado"></div>
## 结合 Tornado
有了 `SessionMixin` 之后,就是结合 Tornado 的使用了。
``` python
class BaseHandler(tornado.web.RequestHandler, SessionMixin):
    session_storage = dict()

    def prepare(self):
        self.open_session()

        if self.session.get('user_id'):
            user_id = self.session['user_id']
            self.current_user = UserModel.get_by_user_id(user_id)

    def finish(self, chunk=None):

        self.save_session()
        super(BaseHandler, self).finish(chunk)

上面是初始化 Session,然后继承 BaseHandler,并且在 RequestHandler 里使用 self.session 就可以操作 Session 了。

## Session Storage 上述的 Session 机制是一个单机版,并且重启程序无效,而且如果通过 Nginx 反代了多个 Tornado Server 的话,Session 并不能公用,为了解决这个,我们可以使用 Redis 来作为 Session 的容器。

首先是一个抽象的 Session Storage 的类

class SessionStorage(object):

    def get(self, key, default=None):
        raise NotImplemented

    def set(self, key, value, expires=None):
        raise  NotImplemented

    def delete(self, key):
        raise NotImplemented

基本实现上述 3 个方法,就可以成为一个 Session 的容器了。

以下是 Redis 作为容器的示例

class RedisStorage(SessionStorage):

    def __init__(self, redis, prefix="wing2"):
        self._redis = redis
        self._prefix = prefix

    def _wrapper(self, key):
        return "session:{prefix}:{key}".format(
            prefix=self._prefix,
            key=key
        )

    def get(self, key, default=None):
        key = self._wrapper(key)
        value = self._redis.get(key)
        if not value:
            return default
        value = pickle.loads(value)
        return value

    def set(self, key, value, expires=None):
        key = self._wrapper(key)
        value = pickle.dumps(value)

        return self._redis.setex(key, value, expires or 86400)  # default one day

    def delete(self, key):
        key = self._wrapper(key)
        return self._redis.delete(key)

以上就是为 Tornado 添加 Session 的示例。

# 感谢 * Flask Session (https://github.com/pallets/flask/blob/master/flask/sessions.py)

EOF