剖析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 30 Python
Python入门教程之运算符与控制流
Aug 17 Python
python GUI实例学习
Nov 21 Python
Python实现PS滤镜中马赛克效果示例
Jan 20 Python
pyqt远程批量执行Linux命令程序的方法
Feb 14 Python
django中使用Celery 布式任务队列过程详解
Jul 29 Python
python+rsync精确同步指定格式文件
Aug 29 Python
Numpy一维线性插值函数的用法
Apr 22 Python
如何对python的字典进行排序
Jun 19 Python
Python利用Faiss库实现ANN近邻搜索的方法详解
Aug 03 Python
python人工智能human learn绘图可创建机器学习模型
Nov 23 Python
Python 绘制多因子柱状图
May 11 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
PHP运行SVN命令显示某用户的文件更新记录的代码
2014/01/03 PHP
php使用FFmpeg接口获取视频的播放时长、码率、缩略图以及创建时间
2016/11/07 PHP
PHP实现查询手机归属地的方法详解
2017/04/28 PHP
php操作redis常见方法示例【key与value操作】
2020/04/14 PHP
FileUpload 控件 禁止手动输入或粘贴的实现代码
2010/04/07 Javascript
Javascript 面向对象之重载
2010/05/04 Javascript
js判断一个字符串是否包含一个子串的方法
2015/01/26 Javascript
引用jquery框架后出错的解决方法
2016/08/09 Javascript
HTML5 canvas 9绘制图片实例详解
2016/09/06 Javascript
JavaScript仿微博输入框效果(案例分析)
2016/12/06 Javascript
IScroll5实现下拉刷新上拉加载的功能实例
2017/08/11 Javascript
Vue 源码分析之 Observer实现过程
2018/03/29 Javascript
vue.js前后端数据交互之提交数据操作详解
2018/04/24 Javascript
node实现的爬虫功能示例
2018/05/04 Javascript
微信小程序实现pdf、word等格式文件上传的方法
2019/09/10 Javascript
layui清空,重置表单数据的实例
2019/09/12 Javascript
antd-日历组件,前后禁止选择,只能选中间一部分的实例
2020/10/29 Javascript
vue监听键盘事件的相关总结
2021/01/29 Vue.js
[56:21]LGD vs IG 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
Python基于pygame实现的弹力球效果(附源码)
2015/11/11 Python
python 实现删除文件或文件夹实例详解
2016/12/04 Python
python getopt详解及简单实例
2016/12/30 Python
linux环境下的python安装过程图解(含setuptools)
2017/11/22 Python
Django 生成登陆验证码代码分享
2017/12/12 Python
Python自动化运维_文件内容差异对比分析
2017/12/13 Python
Python匿名函数/排序函数/过滤函数/映射函数/递归/二分法
2019/06/05 Python
浅析Django 接收所有文件,前端展示文件(包括视频,文件,图片)ajax请求
2020/03/09 Python
python中常用的数据结构介绍
2021/01/12 Python
10分钟理解CSS3 FlexBox弹性布局
2018/12/20 HTML / CSS
如何找出EMP表里面SALARY第N高的employee
2013/12/05 面试题
药学专业毕业生求职信
2013/10/20 职场文书
医药销售求职信范文
2014/02/01 职场文书
计算机网络专业自荐书
2014/06/09 职场文书
民政局离婚协议书范本
2014/10/20 职场文书
php+laravel 扫码二维码签到功能
2021/05/15 PHP
Redis高可用集群redis-cluster详解
2022/03/20 Redis