tornado 06集成session - nuanxin1111/react GitHub Wiki

6.1 SESSION和COOKIE

什么cookie

cookie分为二种

  • 以文件方式存在硬盘空间上的长期性的cookie
  • 停留在浏览器所占内存中的临时性的cookie

浏览网站时,你会经常发现网站登录的地方,会有提示,问你是不是要记住自己的登录状态,像这种情况,登录时填写的一些信息会被以文件的方式存放在客户端的硬盘上。 当用户登录后,session会在cookie端产生一个session_id,这个session_id是存于浏览器所占用的内存当中。当你关闭浏览器后,session_id也要消失了。 cookie采用的是在客户端保持状态的方案,它是客户端的会话状态的一种储存机制。它是服务器在本地机器上存储的小段文本或者是内存中的一段数据,并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头信息向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,或者本地内存中数据,它会自动将同一服务器的任何请求缚上这些cookies,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制借助于cookie机制来达到保存标识的目的,这样就可以解决HTTP协议无状态的缺陷。

什么是session

session是一种服务器端的信息管理机制,它把这些文件信息以文件的形势存放在服务器的硬盘空间上,这种情况是默认的,可以用memcache或redis把这种数据放到内存里面。 其中一种实现方式是,当客户端向服务器发出请求时,要求服务器端产生一个session时,服务器端会先检查一下,客户端的cookie里面有没有session_id,是否已经过期。如果有这样的session_id的话,服务器端会根据cookie里的session_id把服务器的session检索出来。如果没有这样的session_id的话,服务器端会重新建立一个。SESSIONID是一串加了密的字符串,它的生成按照一定的规则来执行。同一客户端启动二次生成这个字符串的话, SESSIONID是不一样的。

6.2 在tornado中集成session

资料:http://developer.51cto.com/art/201405/438313.htm 我们把这个轮子集成到我们的项目中,实现登录。 首先util/session.py

import tornado.web
import hmac
import ujson
import hashlib
import redis
import uuid

#继承字典,session存取模仿字典使用
class SessionData(dict):
    #初始化提供session_id和hmac_key
    def __init__(self, session_id, hmac_key):
        self.session_id = session_id
        self.hmac_key = hmac_key

#继承SessionData
class Session(SessionData):
    #初始化,绑定session_manager和tornado对应的handler
    def __init__(self, session_manager, request_handler):
        self.session_manager = session_manager
        self.request_handler = request_handler

        try:
            #正常访问
            current_session = session_manager.get(request_handler) 
        except InvalidSessionException:
            current_session = session_manager.get()

        #取出current_session中的数据
        for key, data in current_session.iteritems():
            self[key] = data
        # 保存下 session_id 
        self.session_id = current_session.session_id
        # 以及对应的 hmac_key 
        self.hmac_key = current_session.hmac_key

    # 定义 save 方法,用于 session 修改后的保存,实际调用 session_manager 的 set 方法 
    def save(self):
        self.session_manager.set(self.request_handler, self)

class SessionManager(object):
    def __init__(self, secret, store_options, session_timeout):
        self.secret = secret
        self.session_timeout = session_timeout
        try:
            if store_options['redis_pass']:
                self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'], password=store_options['redis_pass'])
            else:
                self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'])
        except Exception as e:
            print e
 def _fetch(self, session_id):
        try:
            session_data = raw_data = self.redis.get(session_id)
            if raw_data != None:
                self.redis.setex(session_id, self.session_timeout, raw_data)
                session_data = ujson.loads(raw_data)
            if type(session_data) == type({}):
                return session_data
            else:
                return {}
        except IOError:
            return {}

    def get(self, request_handler = None):
        if (request_handler == None):
            session_id = None
            hmac_key = None
        else:
            session_id = request_handler.get_secure_cookie("session_id")
            hmac_key = request_handler.get_secure_cookie("verification")
        if session_id == None:
            session_exists = False
            session_id = self._generate_id()
   hmac_key = self._generate_hmac(session_id)
        else:
            session_exists = True
        check_hmac = self._generate_hmac(session_id)
        if hmac_key != check_hmac:
            raise InvalidSessionException()
        session = SessionData(session_id, hmac_key)
        if session_exists:
            session_data = self._fetch(session_id)
            for key, data in session_data.iteritems():
                session[key] = data
        return session

    def set(self, request_handler, session):
        request_handler.set_secure_cookie("session_id", session.session_id)
        request_handler.set_secure_cookie("verification", session.hmac_key)
        session_data = ujson.dumps(dict(session.items()))
        self.redis.setex(session.session_id, self.session_timeout, session_data)

    def _generate_id(self):
        new_id = hashlib.sha256(self.secret + str(uuid.uuid4()))
        return new_id.hexdigest()

    def _generate_hmac(self, session_id):
        return hmac.new(session_id, self.secret, hashlib.sha256).hexdigest()

class InvalidSessionException(Exception):
    pass

现在我们在我们的项目中把这个东西用起来,首先我们已经在settings中配置了关于redis的部分,

   #redis设置
        "store_options":{
            'redis_host':'localhost',    
            'redis_port':6379,
            'redis_pass':'',
        },

然后我们在server.py中初始化:

class Application(tornado.web.Application):
    def __init__(self):
        handlers = urls.urls
        setting = settings.settings
        #connect mongo
        client = pymongo.MongoClient(setting['mongo_options']['hostaddress'])
        self.myMongodb = client[setting['mongo_options']['collection']]

        tornado.web.Application.__init__(self, handlers, **setting)
        self.session_manager = session.SessionManager(
                setting['session_secret'],
                setting['store_options'],
                setting['session_timeout']
                )

然后我们去看看session.py中的SessionManager中的部分:

       self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'], password=store_options['redis_pass'])

需要安装redis模块

pip install redis

我们在SessionManager的__init__中对redis完成了连接。

6.3 用session实现的登录

好了,现在看看我们session的简单应用,用session实现的登录handler .

class LoginHandler(BaseHandler):
    def get(self):
        logging.info("login html")
        self.render("login.html")

    def post(self):
        """handle the login request 
        """
        username = self.get_argument('username')
        passwd = self.get_argument('passwd')

        db = self.application.myMongodb
        user = db.users.find_one({'name':username, 'passwd':passwd})
        if user is not None:
            del user['_id']
            context = {'status':'success'}
            #存储进session
            self.session['username'] = user['name']
            self.session.save()

            self.write(json.dumps(context))
        else:
            context = {'status':'error'}
            self.write(json.dumps(context))

很简单的一个例子,我们判断用户的登录是否合法,如果mongodb数据库中有这条纪录,那么我们把username存储进session. 需要登录使用session的handler都需要继承一个我们自己写的BaseHandler,看看在这个类中我们做了哪些操作.

class BaseHandler(tornado.web.RequestHandler):
    def __init__(self, *argc, **kwarg):
        super(BaseHandler, self).__init__(*argc, **kwarg)
        #定义handler的session,每次访问都会初始化一个Session
        self.session = session.Session(self.application.session_manager, self)

    def get(self):
        """捕获404
        """
        self.send_error(404)

    def get_current_user(self):
        """复写get_current_user方法,在使用登录装饰器的时候判断是否登录
        """
        return self.session.get('username')

    def write_error(self, status_code, **kwargs):
        """重写404错误页
        """
        if status_code == 404:
            self.render("public/404.html")
        elif status_code == 500:
            self.render("public/500.html")
        else:
            self.write('error' + str(status_code))

我们可以在404.html和500.html中做一些自定义的信息,来使提示变得更友善。我们重写了get_current_user方法,在需要登录才能访问的模块需要继承这个类,然后需要使用装饰器@tornado.web.authenticated. 我们在初始化的使用都初始了一个Session供使用。

session生命周期,我们在settings中定义

    #session生存周期 seconds
        "session_timeout":30*60,

注意这个的单位是秒,在控制session的生命周期的时候在这儿控制。