剖析Python的Tornado框架中session支持的实现代码


Posted in Python onAugust 21, 2015

tornado 里面没有 session?不,当然有~我知道 github 上肯定有人帮我写好了~ O(∩_∩)O~
      于是乎,找到下面这个项目,用 memcached 实现 tornado 的 session。光会用可不行啊,让我们看看是怎么写的~

      项目地址:tornado-memcached-sessions
 
      让我们先从 demo 看起....

      app.py 中:
     首先可以注意到,这里定义了一个新的 Application 类,继承于 tornado.web.Application, 在该类的初始化方法中,设定了应用参数 settings, 之后初始化父类和 session_manager.(这是什么?暂时不管它...)

class Application(tornado.web.Application): 
  def __init__(self): 
    settings = dict( 
      # 设定 cookie_secret, 用于 secure_cookie 
      cookie_secret = "e446976943b4e8442f099fed1f3fea28462d5832f483a0ed9a3d5d3859f==78d", 
      # 设定 session_secret 用于生成 session_id 
      session_secret = "3cdcb1f00803b6e78ab50b466a40b9977db396840c28307f428b25e2277f1bcc", 
      # memcached 地址 
      memcached_address = ["127.0.0.1:11211"], 
      # session 过期时间 
      session_timeout = 60, 
      template_path = os.path.join(os.path.dirname(__file__), "templates"), 
      static_path = os.path.join(os.path.dirname(__file__), "static"), 
      xsrf_cookies = True, 
      login_url = "/login", 
    ) 
 
    handlers = [ 
      (r"/", MainHandler), 
      (r"/login", LoginHandler) 
    ] 
 
    # 初始化父类 tornado.web.Application 
    tornado.web.Application.__init__(self, handlers, **settings) 
    # 初始化该类的 session_manager 
    self.session_manager = session.SessionManager(settings["session_secret"], settings["memcached_address"], settings["session_timeout"])

      在下面的 LoginHandler 中我们可以看到 session 的使用:

class LoginHandler(BaseHandler): 
  def get(self): 
    self.render("login.html") 
 
  def post(self): 
    # 以字典的键值对形式存取 
    self.session["user_name"] = self.get_argument("name") 
    # 修改完要调用 session 的 save, 否则等于没有修改哦... 
    self.session.save() 
    self.redirect("/")

     从使用来看是不是非常简洁和清晰?那么,细心的你是不是发现现在的 handler 没有继承于 tornado.web.RequestHandler?带着强烈的探(zuo)索(si)精神我们打开了 base.py。天啊,好短....(噢,你想到哪里去了...)
     BaseHandler 的方法只是初始化,并重写了 get_current_user 的用于用户登录验证的方法。

class BaseHandler(tornado.web.RequestHandler): 
  def __init__(self, *argc, **argkw): 
    super(BaseHandler, self).__init__(*argc, **argkw) 
    # 定义 handler 的 session, 注意,根据 HTTP 特点,每次访问都会初始化一个 Session 实例哦,这对于你后面的理解很重要 
    self.session = session.Session(self.application.session_manager, self) 
 
  # 这是干嘛的?用于验证登录...请 google 关于 tornado.web.authenticated, 其实就是 tornado 提供的用户验证 
  def get_current_user(self): 
    return self.session.get("user_name")

      看到这里,是不是心满意足?噢,我终于理解了!。。。喂,说好的探(zuo)索(si)精神呢?关键在于 session.py 啊!你一脸茫然地回过了头....

首先看看需要的库:
      pickle 一个用于序列化反序列化的库(听不懂?你直接看成和 json 一样作用就行了...)
      hmac 和 hashlib 用于生成加密字符串
      uuid 用于生成一个唯一 id
      memcache  Python 的 memcache 客户端

      这里面有三个类,SessionData Session 和 SessionManager。先看最简单的 SessionData。
      SessionData 用于以字典的结构存储 session 数据,继承于字典,其实只比字典多了两个成员变量:

# 继承字典,因为 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

      
      然后就是真正的 Session 类了。Session 类继承于 SessionData, 注意,它还是十分像内置类型字典,只是重写了自己的初始化方法,并定义了 save 接口——用于保存修改后的 session 数据。

# 继承 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: 
      # 正常是获取该 session 的所有数据,以 SessionData 的形式保存 
      current_session = session_manager.get(request_handler) 
    except InvalidSessionException: 
      # 如果是第一次访问会抛出异常,异常的时候是获取了一个空的 SessionData 对象,里面没有数据,但包含新生成的 
      # session_id 和 hmac_key 
      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)

     
     __init__ 方法比较难理解,基本流程是定义自己的 session_manager 和 handler 处理对象。然后通过 session_manager 获得已有的 session 数据,用这些数据初始化一个访问的用户的 session, 如果用户是第一次访问,那么他拿到的是一个新的 SessionData 对象,因为有可能是新用户,所以这里要对 session_id 和 hmac_key(什么鬼) 进行赋值。
      而 save 方法是提供了对修改 session 数据后的保存接口,实际是调用 session_manager 的 set 方法,具体实现先不考虑。

      看到这两个类,你就应该对 session 的工作有基本理解,可以从用户访问的流程来考虑。注意 BaseHandler 这个入口,每个用户的访问都是一次 HTTP 请求。当用户第一次访问或者上一次的 session 过期了,这时用户访问时 tornado 建立了一个 handler 对象(该 handler 一定继承于 BaseHandler),并且在初始化时建立了一个 session 对象,因为是新访问,所以目前 session 里面没有数据,在之后采用 键/值 对的形式读写 session(不要忘了 Session 具有字典的所有操作),修改后通过 save 方法保存 session。如果用户不是新访问,那么也是按照上述的流程,不过 session 初始化时把 之前的数据取出来保存在该实例中。当用户结束访问,HTTP 断开连接,handler 实例销毁,session 实例销毁(注意,是实例销毁,不是数据销毁)。

      下面准备讲 SessionManager 是吧,来~一个一个函数看~

      首先是初始化,设置密钥, memcache 地址,session 超时时间。

# 初始化需要一个用于 session 加密的 secret, memcache 地址, session 的过期时间 
def __init__(self, secret, memcached_address, session_timeout): 
  self.secret = secret 
  self.memcached_address = memcached_address 
  self.session_timeout = session_timeout

     接着是 _fetch 方法,以 session_id  为键从 memcached 中取出数据,并用 pickle 反序列化解析数据:

# 该方法用 session_id 从 memcache 中取出数据 
def _fetch(self, session_id): 
  try: 
    # 连接 memcache 服务器 
    mc = memcache.Client(self.memcached_address, debug=0) 
    # 获取数据 
    session_data = raw_data = mc.get(session_id) 
    if raw_data != None: 
      # 为了重新刷新 timeout 
      mc.replace(session_id, raw_data, self.session_timeout, 0) 
      # 反序列化 
      session_data = pickle.loads(raw_data) 
    # 如果拿到的数据是字典形式,才进行返回 
    if type(session_data) == type({}): 
      return session_data 
    else: 
      return {} 
  except IOError: 
    return {}

      
      get 经过安全检查后,以 SessionData 的形式返回 memcached 的数据(调用了 _fetch)方法。

def get(self, request_handler = None): 
 
  # 获取对应的 session_id 和 hmac_key 
  if (request_handler == None): 
    session_id = None 
    hmac_key = None 
  else: 
    # session 的基础还是靠 cookie 
    session_id = request_handler.get_secure_cookie("session_id") 
    hmac_key = request_handler.get_secure_cookie("verification") 
 
  # session_id 不存在的时候则生成一个新的 session_id 和 hmac_key 
  if session_id == None: 
    session_exists = False 
    session_id = self._generate_id() 
    hmac_key = self._generate_hmac(session_id) 
  else: 
    session_exists = True 
 
  # 检查 hmac_key 
  check_hmac = self._generate_hmac(session_id) 
  # 不通过则抛出异常 
  if hmac_key != check_hmac: 
    raise InvalidSessionException() 
 
  # 新建 SessionData 对象 
  session = SessionData(session_id, hmac_key) 
 
  if session_exists: 
    # 通过 _fetch 方法获取 memcache 中该 session 的所有数据 
    session_data = self._fetch(session_id) 
    for key, data in session_data.iteritems(): 
      session[key] = data 
 
  return session

     
     至于 set 方法,是为了更新 memcached 的数据。

# 设置新的 session,需要设置 handler 的 cookie 和 memcache 客户端 
def set(self, request_handler, session): 
  # 设置浏览器的 cookie 
  request_handler.set_secure_cookie("session_id", session.session_id) 
  request_handler.set_secure_cookie("verification", session.hmac_key) 
  # 用 pickle 进行序列化 
  session_data = pickle.dumps(dict(session.items()), pickle.HIGHEST_PROTOCOL) 
  # 连接 memcache 服务器 
  mc = memcache.Client(self.memcached_address, debug=0) 
  # 写入 memcache 
  mc.set(session.session_id, session_data, self.session_timeout, 0)

     
     最后的两个函数,一个是生成 session_id,另一个用 session_id 与密钥加密后生成一个加密字符串,用于验证。

# 生成 session_id 
def _generate_id(self): 
  new_id = hashlib.sha256(self.secret + str(uuid.uuid4())) 
  return new_id.hexdigest() 
 
# 生成 hmac_key 
def _generate_hmac(self, session_id): 
  return hmac.new(session_id, self.secret, hashlib.sha256).hexdigest()

      我们在哪里初始化了 SessionManager 呢?还记得第一篇里面的 Application 类吗?噢...快回去翻翻。

Python 相关文章推荐
详解Python中的循环语句的用法
Apr 09 Python
python下MySQLdb用法实例分析
Jun 08 Python
对Python闭包与延迟绑定的方法详解
Jan 07 Python
Python OpenCV利用笔记本摄像头实现人脸检测
Aug 20 Python
使用python将多个excel文件合并到同一个文件的方法
Jul 09 Python
pycharm显示远程图片的实现
Nov 04 Python
pandas实现将日期转换成timestamp
Dec 07 Python
python 中的[:-1]和[::-1]的具体使用
Feb 13 Python
如何实现在jupyter notebook中播放视频(不停地展示图片)
Apr 23 Python
Pytorch十九种损失函数的使用详解
Apr 29 Python
使用Python+Appuim 清理微信的方法
Jan 26 Python
总结Python常用的魔法方法
May 25 Python
约瑟夫问题的Python和C++求解方法
Aug 20 #Python
在类Unix系统上开始Python3编程入门
Aug 20 #Python
Python中字典映射类型的学习教程
Aug 20 #Python
Python3字符串学习教程
Aug 20 #Python
Python中列表和元组的相关语句和方法讲解
Aug 20 #Python
编写Python脚本抓取网络小说来制作自己的阅读器
Aug 20 #Python
使用Python求解最大公约数的实现方法
Aug 20 #Python
You might like
国产动画《伍六七》原声大碟大卖,啊哈娱乐引领音乐赋能IP的新尝试
2020/03/08 国漫
浅析PHP递归函数返回值使用方法
2013/02/18 PHP
PHP递归删除目录几个代码实例
2014/04/21 PHP
PHP的伪随机数与真随机数详解
2015/05/27 PHP
php数据访问之增删改查操作
2016/05/09 PHP
PHP实现的最大正向匹配算法示例
2017/12/19 PHP
去除链接虚线全面分析总结
2006/08/15 Javascript
js 字符串操作函数
2009/07/25 Javascript
javascript圆盘抽奖程序实现原理和完整代码例子
2014/06/03 Javascript
javascript异步编程代码书写规范Promise学习笔记
2015/02/11 Javascript
javascript实现别踩白块儿小游戏程序
2015/11/22 Javascript
jQuery制作圣诞主题页面 更像是爱情影集
2016/08/10 Javascript
JS中数组重排序方法
2016/11/11 Javascript
AngularJS入门教程之Helloworld示例
2016/12/25 Javascript
react.js 翻页插件实例代码
2017/01/19 Javascript
vue按需引入element Transfer 穿梭框
2017/09/30 Javascript
浅谈React Native Flexbox布局(小结)
2018/01/08 Javascript
vue实现点击按钮“查看详情”弹窗展示详情列表操作
2020/09/09 Javascript
vue pages 多入口项目 + chainWebpack 全局引用缩写说明
2020/09/21 Javascript
[11:57]《一刀刀一天》第十七期:TI中国军团加油!
2014/05/26 DOTA
[01:52]2020年DOTA2 TI10夏季活动预告片
2020/07/15 DOTA
Python中将字典转换为XML以及相关的命名空间解析
2015/10/15 Python
Django REST framework 单元测试实例解析
2019/11/07 Python
python seaborn heatmap可视化相关性矩阵实例
2020/06/03 Python
keras 实现轻量级网络ShuffleNet教程
2020/06/19 Python
LA MER海蓝之谜美国官网:传奇面霜
2016/08/27 全球购物
北美大型运动类产品商城:Champs Sports
2017/01/12 全球购物
西班牙国家航空官方网站:Iberia
2017/11/16 全球购物
最新远光软件笔试题面试题内容
2013/11/08 面试题
Java的类与C++的类有什么不同
2014/01/18 面试题
自我反省检讨书
2014/01/23 职场文书
班组长安全工作职责
2014/07/15 职场文书
2014年基层党支部工作总结
2014/12/04 职场文书
模范教师材料大全
2014/12/16 职场文书
《地震中的父与子》教学反思
2016/02/16 职场文书
教你用eclipse连接mysql数据库
2021/04/22 MySQL