首页 > 代码库 > Tornado web框架

Tornado web框架

本节目录

1.介绍

2.tornado web应用的结构

3、路由系统

4、模板引擎

5、静态文件与文件缓存

6、cookies与secure cookie

1、介绍

tornado是一个Python web框架和异步网络库 起初由 FriendFeed 开发. 通过使用非阻塞网络I/O, Tornado 可以支持上万级的连接,处理 长连接, WebSockets, 和其他 需要与每个用户保持长久连接的应用.

Tornado 大体上可以被分为4个主要的部分:

  • web框架 (包括创建web应用的 RequestHandler 类,还有很多其他支持的类).
  • HTTP的客户端和服务端实现 (HTTPServer and AsyncHTTPClient).
  • 异步网络库 (IOLoop and IOStream), 为HTTP组件提供构建模块,也可以用来实现其他协议.
    • ioloop.py 主要的是将底层的epoll或者说是其他的IO多路复用封装作异步事件来处理。
    • iostream.py主要是对于下层的异步事件的进一步封装,为其封装了更上一层的buffer(IO)事件。
  • 协程库 (tornado.gen) 允许异步代码写的更直接而不用链式回调的方式.

Tornado web 框架和HTTP server 一起为 WSGI 提供了一个全栈式的选择. 在WSGI容器 (WSGIAdapter) 中使用Tornado web框架或者使用Tornado HTTP server 作为一个其他WSGI框架(WSGIContainer)的容器,这样的组合方式都是有局限性的. 为了充分利用Tornado的特性,你需要一起使用Tornado的web框架和HTTP server.

2、Tornado web应用的结构

  通常一个Tornado web应用包括一个或者多个 RequestHandler 子类, 一个可以将收到的请求路由到对应handler的 Application 对象,和 一个启动服务的 main() 函数.

  一个简单的tornado应用:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        # self.write("Hello, world")  被用于非模板基础的输 出; 它接受字符串, 字节, 和字典(字典会被编码成JSON).
        # self.render(‘s1.html‘)  #渲染模板文件,默认在当前目录下找
        # self.render(‘template/s1.html‘)#html文件集中放在template文件夹中,让其在该文件夹下找
        self.render(s1.html) #如果我们不想每次都写template的话,我们可以配置一下它,让其默认就是在该路径下查找
self.get_argument(‘xxx‘,None) # 获取用户提交的数据
#配置
settings={
    template_path:template ,#模板路径的配置
    static_path:static  #静态文件必须要给它配置一下,不然加载是找不到的
}
#路由映射,路由系统
application = tornado.web.Application([
    (r"/index", MainHandler),
] ,**settings)#应用到tornado中,使其生效

# Application对象是负责全局配置的, 包括映射请求转发给处理程序的路由表.


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Application对象

  Application对象是负责全局配置的, 包括映射请求转发给处理程序的路由表.

  路由表是 URLSpec 对象(或元组)的列表, 其中每个都包含(至少)一个正则 表达式和一个处理类. 顺序问题; 第一个匹配的规则会被使用. 如果正则表达 式包含捕获组, 这些组会被作为 路径参数 传递给处理函数的HTTP方法. 如果一个字典作为 URLSpec 的第三个参数被传递, 它会作为 初始参数 传递给RequestHandler.initialize. 最后 URLSpec 可能有一个名字 , 这将允许它被RequestHandler.reverse_url 使用.

  例如, 在这个片段中根URL / 映射到了 MainHandler , 像 /story/ 后跟着一个数字这种形式的URL被映射到了 StoryHandler. 这个数字被传递(作为字符串)给 StoryHandler.get.

class MainHandler(RequestHandler):
    def get(self):
        self.write(<a href="http://www.mamicode.com/%s">link to story 1</a> %
                   self.reverse_url("story", "1"))

class StoryHandler(RequestHandler):
    def initialize(self, db):
        self.db = db

    def get(self, story_id):
        self.write("this is story %s" % story_id)

app = Application([
    url(r"/", MainHandler),
    url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
    ])

  Application 构造函数有很多关键字参数可以用于自定义应用程序的行为 和使用某些特性(或者功能); 完整列表请查看 Application.settings .

3、路由系统

  在web框架中,路由表中的任意一项是一个元组,每个元组包含pattern(模式)和handler(处理器)。当httpserver接收到一个http请求,server从接收到的请求中解析出url path(http协议start line中),然后顺序遍历路由表,如果发现url path可以匹配某个pattern,则将此http request交给web应用中对应的handler去处理。

  路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。路由表,卸耦了http server层和web application层。

  路由表具有非常重要的作用,卸耦了http server层和web application层。由于有了url路由机制,web应用开发者不必和复杂的http server层代码打交道,只需要写好web应用层的逻辑(handler)即可。

  Application类位于web.py,一个Application类的实例相当于一个web应用。

application = tornado.web.Application([
        (r"/index", MainHandler),
    ])

#把一个路由表(一个列表)作为参数,传递给Application类的构造函数,创建了一个实例,然后再把这个实例传递给http_server。那么当客户端发起”get /”请求的时候,http server接收到这个请求,在理由表中匹配url pattern,最后交给MainHandler去处理。

路由表 

  tornado原生支持RESTful比如我给用户提供了查询书、购买书、取消购买书的功能,那按理说我需要给用户三个url,查询书比如说是:http://www.book.com:8888/search,购买书是:http://www.book.com:8888/buy,取消购买书是:http://www.book.com:8888/delete。用户访问不同的url进行他需要的操作。上面仅仅是三个基本的功能,一个网站提供的功能肯定特别多,那岂不是要给用户很多个url?那有没有更简便的方式呢,客户端和服务端进行一个约定,都只需要维护一个url就行了,比如下面这种:

# 客户端和服务端都只维护下面这一个url
书本url:http://www.book.com:8888/index

# 客户端和服务端通过不同的method来执行不同的操作
method:get
method:post
method:delete
method:put

  上面的这种约定是一种软件架构方式:RESTful,双方约定好怎么获取服务和提供服务,然后只维护一个url、通过改变请求的method来通信。这也是“面向资源编程”的概念,将网络中的所有东西都视为资源。Tornado原生支持RESTful,这也是其优势之一。

技术分享
import tornado.ioloop
import tornado.web

user_info = []
class MainHandler(tornado.web.RequestHandler):

# 用户以get方式访问,就执行get方法
    def get(self):
        # self.write("Hello, world")
        self.render(index.html)

# 用户以post方式访问,就执行post方法
    def post(self, *args, **kwargs):
        self.write(post)

# 用户以delete方式访问,就执行delete方法
    def delete(self):
        self.write("delete")


settings = {
    template_path: template,
    static_path: static,
}


application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8888)
    # epoll + socket
    tornado.ioloop.IOLoop.instance().start()
demo

 tornado原生支持二级域名

域名的划分:

  .com 顶级域名
  baidu.com 一级域名
  www.baidu.com 二级域名
  bbs.baidu .com 二级域名
  tieba.baidu .com 二级域名
注意:很多人都误把带www当成一级域名,把其他前缀的当成二级域名,这是错误的,其实www.baidu.com 与bbs.baidu .com同为二级域名,只是人们习惯使用www为前缀的二级域名作为网站的主域名入口罢了。 

  我们可以使用二级域名用来区分不同的业务,tornado在url划分的时候,比django更加友好;django是在主域名后面接着2级域名,配置使用include 进行业务分类url。

技术分享
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("www")

class CmdbMainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("cmdb")
        
settings = {
    template_path: template,
    static_path: static,
}

# 默认二级域名是www;用户输入www.freely.com:8888/index时,执行MainHandler里的方法。
application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)

# 用户输入的域名是cmdb.freely.com:8888/index时,执行CmdbMainHandler里的方法。
application.add_handlers("cmdb.freely.com",#重新设置url使用add_handlers方法,第一个参数是2级域名名称,后面是url列表。
[
    (r"/index", CmdbMainHandler),
], **settings)

# django里就是www.freely.com:8000/index、www.freely.com:8000/cmdb这样。
View Code

4、模板引擎

  Tornado中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

  Tornado =的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}

  控制语句和对应的 Python 语句的格式基本完全相同。我们支持 ifforwhile 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块 的代码文档中有着详细的描述。

  tornado模 板语言,无论for或者if,结尾都是end,不像django的endfor、endif;另外,tornado模板语言,取数据时跟python一模一样,如下面的取字典里的数据,可以直接dict[‘key‘],也可以dict.get(‘key‘,‘default‘);不像django里的item.1。

  注:在使用模板前需要在setting中设置模板路径:"template_path" : "tpl"

1.基本使用

技术分享
import tornado.ioloop
import tornado.web
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", list_info = [11,22,33],title=Mytitle)
  
application = tornado.web.Application([
    (r"/index", MainHandler),
])
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
app.py
技术分享
<html>
   <head>
      <title>{{ title }}</title>
   </head>
   <body>
     <ul>
       {% for item in list_info %}
         <li>{{item)}}</li>
       {% end %}
     </ul>
   </body>
 </html>
index.html

  表达式可以是任意的Python表达式, 包括函数调用. 模板代码会在包含以下对象 和函数的命名空间中执行 (注意这个列表适用于使用 RequestHandler.render 和 render_string 渲染模板的情况. 如果你直接在 RequestHandler 之外使用 tornado.template 模块, 下面这些很多都不存 在)

技术分享
在模板中默认提供了一些函数、字段、类以供模板使用:

escape: tornado.escape.xhtml_escape 的別名
xhtml_escape: tornado.escape.xhtml_escape 的別名
url_escape: tornado.escape.url_escape 的別名
json_encode: tornado.escape.json_encode 的別名
squeeze: tornado.escape.squeeze 的別名
linkify: tornado.escape.linkify 的別名
datetime: Python 的 datetime 模组
handler: 当前的 RequestHandler 对象
request: handler.request 的別名
current_user: handler.current_user 的別名
locale: handler.locale 的別名
_: handler.locale.translate 的別名
static_url: for handler.static_url 的別名
xsrf_form_html: handler.xsrf_form_html 的別名

其他方法
View Code

2.继承

技术分享
<html>
<body>
    <header>
        {% block header %}{% end %}
    </header>
    <content>
        {% block body %}{% end %}
    </content>
    <footer>
        {% block footer %}{% end %}
    </footer>
</body>
</html>
layout.html

当我们扩展父模板layout.html时,可以在子模板index.html中引用这些块。

技术分享
{% extends "layout.html" %}

{% extends "filename.html" %}
{% block header %}
    <h1>{{ header_text }}</h1>
{% end %}

{% block body %}
    <p>Hello from the child template!</p>
{% end %}

{% block footer %}
    <p>{{ footer_text }}</p>
{% end %}
View Code

3、导入(include)

技术分享
<div>
    <ul>
        <li>1024</li>
        <li>42区</li>
    </ul>
</div>
header.html
技术分享
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>title</title>
    <link href=http://www.mamicode.com/"{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div class="pg-header">
        {% include header.html %}
    </div>
    
    <script src=http://www.mamicode.com/"{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>
index.html

4、自定义UIMethod以UIModule

a. 定义

技术分享
# uimethods.py
 
def tab(self):
    return UIMethod
uimethods.py
技术分享
from tornado.web import UIModule
from tornado import escape

class custom(UIModule):

    def render(self, *args, **kwargs):
        return escape.xhtml_escape(<h1>hello world</h1>)
       
uimodules.py

b. 注册

技术分享
import tornado.ioloop
import tornado.web
from tornado.escape import linkify
import uimodules as md
import uimethods as mt

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(index.html)

settings = {
    template_path: template,
    static_path: static,
    static_url_prefix: /static/,
    ui_methods: mt,
    ui_modules: md,
}

application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)


if __name__ == "__main__":
    application.listen(8009)
    tornado.ioloop.IOLoop.instance().start()
View Code

c. 使用

技术分享
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link href=http://www.mamicode.com/"{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
    {% module custom(123) %}
    {{ tab() }}
</body>
View Code

5、模板的自动转义问题

  Tornado默认会自动转义模板中的内容,把标签转换为相应的HTML实体。这样可以防止后端为数据库的网站被恶意脚本攻击。比如,你的网站中有一个评论部分,用户可以在这里添加任何他们想说的文字进行讨论。虽然一些HTML标签在标记和样式冲突时不构成重大威胁(如评论中没有闭<h1>标签),但<script>标签会允许攻击者加载其他的JavaScript文件,打开通向跨站脚本攻击、XSS或漏洞之门。

  所有模板输出默认都会使用 tornado.escape.xhtml_escape 函数转义. 这个行为可以通过传递 autoescape=None 给 Application 或者 tornado.template.Loader 构造器来全局改变, 对于一个模板文件可以使 用 {% autoescape None %} 指令, 对于一个单一表达式可以使用 {% raw ...%} 来代替 {{ ... }}. 此外, 在每个地方一个可选的 转义函数名可以被用来代替 None.

  方法一:是在Application构造函数中传递autoescape=None,另一种方法是在每页的基础上修改自动转义行为,如下所示:

{% autoescape None %}
{{ mailLink }}

这些autoescape块不需要结束标签,并且可以设置xhtml_escape来开启自动转义(默认行为),或None来关闭。

  然而,在理想的情况下,你希望保持自动转义开启以便继续防护你的网站。因此,你可以使用{% raw %}指令来输出不转义的内容。

{% raw mailLink %}

5、静态文件和文件缓存

  1、在应用配置 settings 中指定 static_path 选项来提供静态文件服务;

    2、在应用配置 settings 中指定 static_url_prefix 选项来提供静态文件前缀服务;

  3、在导入静态文件时用 {{static_url(‘XX.css‘)}} 方式实现主动缓存静态文件

Tornado中, 你可以通过在应用程序中指定特殊的 static_path 来提供静态文 件服务:

技术分享
settings = {
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
    "login_url": "/login",
    "xsrf_cookies": True,
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
    (r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
     dict(path=settings[static_path])),
], **settings)
View Code

  为了改善性能, 通常情况下, 让浏览器主动缓存静态资源是个好主意, 这样浏览器 就不会发送不必要的可能在渲染页面时阻塞的 If-Modified-Since 或 Etag 请求了. Tornado使用 静态内容版本(static content versioning) 来支持此项功能.

关于缓存的内容:http://www.cnblogs.com/_franky/archive/2012/07/05/2577141.html

为了使用这些功能, 在你的模板中使用 static_url 方法 而不是直接在你的HTML中输入静态文件的URL:

技术分享
<html>
   <head>
      <title>FriendFeed - {{ _("Home") }}</title>
   </head>
   <body>
     <div><img src=http://www.mamicode.com/"{{ static_url("images/logo.png") }}"/></div>
   </body>
 </html>
View Code

  因为参数 v 是基于文件内容的, 如果你更新一个文件并重启服务, 它将发送 一个新的 v 值, 所以用户的浏览器将会自动的拉去新的文件. 如果文件的内 容没有改变, 浏览器将会继续使用本地缓存的副本, 而不会从服务器检查更新, 显著的提高了渲染性能.

  在生产中, 你可能想提供静态文件通过一个更优的静态服务器, 比如 nginx . 你可以配置任何web服务器识别通过 static_url() 提供的版本标签并相应的设置缓存头. 下面是我们在 FriendFeed 使用的nginx相关配置的一部分:

技术分享
location /static/ {
    root /var/friendfeed/static;
    if ($query_string) {
        expires max;
    }
 }
View Code

6、Cookies 和 secure cookies

  Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等,服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。

cookie的作用

  服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。

1.基本操作

Cookie的操作

  简单来说,Cookie 是保存在浏览器的一个键值对,每次的HTTP请求都会携带 Cookie 。

获取所有的Cookies

self.cookies

设置Cookie

self.set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None, **kwargs)
get_cookie, set_cookie普通的设置cookie, clear_cookie, clear_all_cookies是删除cookie。

可接受的参数描述:

参数描述
name Cookie的Key
value Cookie的value
domain 生效的域名
expires 以秒为过期时间,默认从 1970-01-01T00:00:10.000Z
path 生效路径
expires_days 以天数过期时间,如果设置为 None 则关闭浏览器Cookie就失效
技术分享
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_cookie("mycookie"):
            self.set_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")
View Code

2、加密cookie(签名)

  Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

设置一个加密Cookie所需要的参数:

self.set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs):

实例:

技术分享
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_secure_cookie("mycookie"):
            self.set_secure_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")
             
application = tornado.web.Application([
    (r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
View Code
技术分享
def _create_signature_v1(secret, *parts):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
    for part in parts:
        hash.update(utf8(part))
    return utf8(hash.hexdigest())

# 加密
def _create_signature_v2(secret, s):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
    hash.update(utf8(s))
    return utf8(hash.hexdigest())

def create_signed_value(secret, name, value, version=None, clock=None,
                        key_version=None):
    if version is None:
        version = DEFAULT_SIGNED_VALUE_VERSION
    if clock is None:
        clock = time.time

    timestamp = utf8(str(int(clock())))
    value = base64.b64encode(utf8(value))
    if version == 1:
        signature = _create_signature_v1(secret, name, value, timestamp)
        value = b"|".join([value, timestamp, signature])
        return value
    elif version == 2:
        # The v2 format consists of a version number and a series of
        # length-prefixed fields "%d:%s", the last of which is a
        # signature, all separated by pipes.  All numbers are in
        # decimal format with no leading zeros.  The signature is an
        # HMAC-SHA256 of the whole string up to that point, including
        # the final pipe.
        #
        # The fields are:
        # - format version (i.e. 2; no length prefix)
        # - key version (integer, default is 0)
        # - timestamp (integer seconds since epoch)
        # - name (not encoded; assumed to be ~alphanumeric)
        # - value (base64-encoded)
        # - signature (hex-encoded; no length prefix)
        def format_field(s):
            return utf8("%d:" % len(s)) + utf8(s)
        to_sign = b"|".join([
            b"2",
            format_field(str(key_version or 0)),
            format_field(timestamp),
            format_field(name),
            format_field(value),
            b‘‘])

        if isinstance(secret, dict):
            assert key_version is not None, Key version must be set when sign key dict is used
            assert version >= 2, Version must be at least 2 for key version support
            secret = secret[key_version]

        signature = _create_signature_v2(secret, to_sign)
        return to_sign + signature
    else:
        raise ValueError("Unsupported version %d" % version)

# 解密
def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
    parts = utf8(value).split(b"|")
    if len(parts) != 3:
        return None
    signature = _create_signature_v1(secret, name, parts[0], parts[1])
    if not _time_independent_equals(parts[2], signature):
        gen_log.warning("Invalid cookie signature %r", value)
        return None
    timestamp = int(parts[1])
    if timestamp < clock() - max_age_days * 86400:
        gen_log.warning("Expired cookie %r", value)
        return None
    if timestamp > clock() + 31 * 86400:
        # _cookie_signature does not hash a delimiter between the
        # parts of the cookie, so an attacker could transfer trailing
        # digits from the payload to the timestamp without altering the
        # signature.  For backwards compatibility, sanity-check timestamp
        # here instead of modifying _cookie_signature.
        gen_log.warning("Cookie timestamp in future; possible tampering %r",
                        value)
        return None
    if parts[1].startswith(b"0"):
        gen_log.warning("Tampered cookie %r", value)
        return None
    try:
        return base64.b64decode(parts[0])
    except Exception:
        return None


def _decode_fields_v2(value):
    def _consume_field(s):
        length, _, rest = s.partition(b:)
        n = int(length)
        field_value = rest[:n]
        # In python 3, indexing bytes returns small integers; we must
        # use a slice to get a byte string as in python 2.
        if rest[n:n + 1] != b|:
            raise ValueError("malformed v2 signed value field")
        rest = rest[n + 1:]
        return field_value, rest

    rest = value[2:]  # remove version number
    key_version, rest = _consume_field(rest)
    timestamp, rest = _consume_field(rest)
    name_field, rest = _consume_field(rest)
    value_field, passed_sig = _consume_field(rest)
    return int(key_version), timestamp, name_field, value_field, passed_sig


def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
    try:
        key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
    except ValueError:
        return None
    signed_string = value[:-len(passed_sig)]

    if isinstance(secret, dict):
        try:
            secret = secret[key_version]
        except KeyError:
            return None

    expected_sig = _create_signature_v2(secret, signed_string)
    if not _time_independent_equals(passed_sig, expected_sig):
        return None
    if name_field != utf8(name):
        return None
    timestamp = int(timestamp)
    if timestamp < clock() - max_age_days * 86400:
        # The signature has expired.
        return None
    try:
        return base64.b64decode(value_field)
    except Exception:
        return None


def get_signature_key_version(value):
    value = utf8(value)
    version = _get_version(value)
    if version < 2:
        return None
    try:
        key_version, _, _, _, _ = _decode_fields_v2(value)
    except ValueError:
        return None

    return key_version

内部算法
内部算法

签名Cookie的本质是:

写cookie过程:

  • 将值进行base64加密
  • 对除值以外的内容进行签名,哈希算法(无法逆向解析)
  • 拼接 签名 + 加密值

读cookie过程:

  • 读取 签名 + 加密值
  • 对签名进行验证
  • base64解密,获取值内容

签名后的cookie除了时间戳和一个 HMAC 签名还包含编码 后的cookie值. 如果cookie过期或者签名不匹配, get_secure_cookie 将返回 None 就像没有设置cookie一样

注:许多API验证机制和安全cookie的实现机制相同。

3、用户认证

  当前已经通过认证的用户在每个请求处理函数中都可以通过 self.current_user 得到, 在每个模板中 可以使用 current_user 获得. 默认情况下, current_user 是 None.

  为了在你的应用程序中实现用户认证, 你需要在你的请求处理函数中复写 get_current_user() 方法来判断当前用户, 比如可以基于cookie的值. 这里有一个例子, 这个例子允许用户简单的通过一个保存在cookie中的特殊昵称 登录到应用程序中:

  你可以使用 Python 装饰器(decorator) tornado.web.authenticated 要求用户登录. 如果请求方法带有这个装饰器 并且用户没有登录, 用户将会被重定向到 login_url (另一个应用设置)

  如果你使用 authenticated 装饰 post() 方法并且用户没有登录, 服务将返回一个 403 响应. @authenticated 装饰器是 if not self.current_user: self.redirect() 的简写. 可能不适合 非基于浏览器的登录方案.

技术分享
import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):
 
    def get(self):
        login_user = self.get_secure_cookie("login_user", None)
        if login_user:
            self.write(login_user)
        else:
            self.redirect(/login)
 
class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.current_user()
 
        self.render(login.html, **{status: ‘‘})
 
    def post(self, *args, **kwargs):
 
        username = self.get_argument(name)
        password = self.get_argument(pwd)
        if username == wupeiqi and password == 123:
            self.set_secure_cookie(login_user, Li)
            self.redirect(/)
        else:
            self.render(login.html, **{status: 用户名或密码错误})
 
settings = {
    template_path: template,
    static_path: static,
    static_url_prefix: /static/,
    cookie_secret: aiuasdhflashjdfoiuashdfiuh
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
基于cookie实现用户验证_demo
技术分享
import tornado.ioloop
import tornado.web
 
class BaseHandler(tornado.web.RequestHandler):
 
    def get_current_user(self):
        return self.get_secure_cookie("login_user")
 
class MainHandler(BaseHandler):
 
    @tornado.web.authenticated
    def get(self):
        login_user = self.current_user
        self.write(login_user)
 
 
 
class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.current_user()
 
        self.render(login.html, **{status: ‘‘})
 
    def post(self, *args, **kwargs):
 
        username = self.get_argument(name)
        password = self.get_argument(pwd)
        if username == wupeiqi and password == 123:
            self.set_secure_cookie(login_user, Li)
            self.redirect(/)
        else:
            self.render(login.html, **{status: 用户名或密码错误})
 
settings = {
    template_path: template,
    static_path: static,
    static_url_prefix: /static/,
    cookie_secret: aiuasdhflashjdfoiuashdfiuh,
    login_url: /login
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
基于签名cookie实现用户验证_demo

 

tornado源码分析:http://www.nowamagic.net/academy/detail/13321013

Tornado web框架