Tornado框架

概述

Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。

Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。

安装

pip3 insatll tornado

快速上手

#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
  
application = tornado.web.Application([
    (r"/index", MainHandler),
])
  
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

第一步:执行脚本,监听 8888 端口

第二步:浏览器客户端访问 /index --> http://127.0.0.1:8888/index

第三步:服务器接受请求,并交由对应的类处理该请求

第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法

第五步:方法返回值的字符串内容发送浏览器

路由系统

路由系统其实就是URL和类的对应关系,一般其他框架是URL和函数的对应关系,但在tornado中是类和URL的对应关系.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
   
import tornado.ioloop
import tornado.web
   
   
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
   
class StoryHandler(tornado.web.RequestHandler):
    def get(self, story_id):
        self.write("You requested the story " + story_id)
   
class BuyHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("buy.wupeiqi.com/index")
   
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/story/([0-9]+)", StoryHandler),
])

#添加了二级域名   
application.add_handlers('buy.wupeiqi.com$', [
    (r'/index',BuyHandler),
])
   
if __name__ == "__main__":
    application.listen(80)
    tornado.ioloop.IOLoop.instance().start()

来看下语法:

语法

原生支持restful:

# Author:Alex Li
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# pip3 install tornado
import tornado.ioloop
import tornado.web
import hashlib
import time
def md5(arg):
    m = hashlib.md5()
    m.update(bytes(arg,encoding='utf-8'))
    return m.hexdigest()

app_id_list = [
    '1suidfmsdfoj',
    '1su5451254212'
]
visited_api = [
    "http://www.oldboy.com:8888/api?app_id=8e69354826691f0ea816d7108be7c122|1477194640.0149357"
]

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        app_id = self.get_argument('app_id',None)
        md, current_time = app_id.split('|')
        server_current_time = time.time()
        client_current_time = float(current_time)
        if client_current_time< server_current_time-10:
            self.write('滚')
            return
        # 10s以内发送的数据
        # 正常用户发送请求: 11:10:10
        # 服务器请求: 11:10:12
        if app_id in visited_api:
            self.write('滚')
            return

        # current_time客户端的当前时间
        # 服务器的当前事件


        # 将黑客随便写的url过滤
        flag = False
        for key in app_id_list:
            new_str = "%s|%s" %(key, current_time)
            server_md = md5(new_str)
            if md == server_md:
                # redis,10s之后就消失
                visited_api.append(app_id)
                flag = True
                break
        if flag:
            self.write('ok')
        else:
            self.write('滚')

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

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

访问客户端:client1.py

import requests
import hashlib
import time
def md5(arg):
    m = hashlib.md5()
    m.update(bytes(arg,encoding='utf-8'))
    return m.hexdigest()

key = '1suidfmsdfoj'
current_time = time.time()

new_str = "%s|%s" %(key,current_time)
print(new_str)
md5_new_str = md5(new_str)
print(md5_new_str)
app_id = "%s|%s" %(md5_new_str,current_time)
print(app_id)
url = 'http://www.oldboy.com:8888/api?app_id=%s' %app_id
print(url)
res = requests.get(url)
print(res.text)

牛逼点的黑客访问:client2.py

import requests
key = '1suidfmsdfoj'
res = requests.get('http://www.oldboy.com:8888/api?app_id=%s' %key)
print(res.text)

普通黑客:client3.py

import requests

res = requests.get('http://www.oldboy.com:8888/api?app_id=f2f8c3935f69408a1370efd5e547f46e|1477194151.3949883')
print(res.text)

settings配置参数

settings是一个变量,名称是固定的,里面的key名称也是需要固定的:

template_path       模板的路径
ui_methods      自定义模板语言,定义方式为函数,使用方式{{ method }} 
ui_modules      自定义模板语言,定义方式为类,调用方式为{{
module module_name() }}
debug=ture              开发模式 
'static_url_prefix': '/static/',        url访问静态文件的路径
static_path     静态文件的路径

####################################

Tornado 官方教程上的 settings 是这样的:

settings = {
    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
    "login_url": "/login",
    "xsrf_cookies": True,
}

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)
当然也设置settings字典可以这样:

settings = dict(
    cookie_secret= "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
    login_url= "/login",
    xsrf_cookies= True,
)
settings可以设置什么?

#设置templates路径:
template_path = os.path.join(os.path.dirname(__file__), "templates")

#设置静态文件解析路径:
static_path = os.path.join(os.path.dirname(__file__), "static"),

#设置防跨站请求攻击:
xsrf_cookies = True,
#默认为False,即不可防御。

#设置登陆路径,未登陆用户在操作时跳转会用到这个参数:
login_url = "/login-do",
#默认为@tornado.web.authenticated

#设置调试模式:
debug = True,
#默认为False,即不是调试模式。

#设置cookie密钥:
cookie_secret = "dskfhisdjklagkfdklag;lkjasdklgjkldsjaklgjkldsfksdklf"
#默认为字符串"secure cookies"

#设置是否自动编码:在2.0以上需要设置此项来兼容您之前的APP
autoescape = None,
#不设置默认为自动编码。

#设置template_loader,可以从独立的路径中导入template:
template_loader=utils.ZipLoader,
#其中utils为自己定义的模块,ZipLoader是tornado.template.BaseLoader的子类。

#设置gzip压缩:
gzip=True

#设置静态路径头部:
static_url_prefix = "/mystatic/",
#默认是"/static/"

#设置静态文件处理类:
static_handler_class = MyStaticFileHandler,
#默认是tornado.web.StaticFileHandler

#设置静态文件的参数:
static_handler_args = { "key1":"value1", "key2":"value2"  }
#默认为空字典。

#设置日志处理函数
log_function = your_fun,
# 日志处理函数your_fun,按照自己的意图记录日志。
调试模式的缺点是:只感知.py文件的改变,模版的改变不会加载,有些特殊的错误,比如import的错误,就会直接让服务下线,到时候还得手动重启。还有就是调试模式和 HTTPServer 的多进程模式不兼容。在调试模式下,你必须将 HTTPServer.start 的参数设为不大于 1 的数字。

模板语言

tornado的模板语言跟django的很像,工作原理一致:模板引擎将模板文件载入内存,然后将数据嵌入其中,最终得到一个完整的字符串,再将字符串返回给请求者.

tornado的模板语言支持控制语言表达语言,控制语句是使用{% xxx %}包裹起来的,例如{% if len(items) > 3 %}.表达式语句是使用{{ xxx }}包裹起来的,例如{{ item[0] }}.

控制语句和对应的python语句格式基本相同,支持if else for while try,但和Django不通的是,需要用{% end %}做标记,还可以通过extendsblock实现模板的继承和嵌套.

我们可以总结下tornado和django中的模板语言区别:

  1. 控制语句的结束标记:tornado:{% end %},django:{% endfor %}等.
  2. 索引取值:tornado:与python取值完全相同,django:items.0

来看下例子吧:

首先需要设置一个变量:

settings = {
    'static_path' = 'tpl'
}

模板语言的基本使用

app.py 文件:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", list_info = [11,22,33])
  
application = tornado.web.Application([
    (r"/index", MainHandler),
])
  
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

index.html 文件:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>模板测试</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div>
        <ul>
            {% for item in list_info %}
                <li>{{item}}</li>
            {% end %}
        </ul>
    </div>
    
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>

另外在模板语言中,提供了一些函数 字段 类 供模板使用:

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 的別名

母板

layout.html 母板: 跟django中的使用方式大致相同

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>test</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    {% block CSS %}{% end %}
</head>
<body>

    <div class="pg-header">

    </div>
    
    {% block RenderBody %}{% end %}
   
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
    {% block JavaScript %}{% end %}
</body>
</html>

index.html

{% extends 'layout.html'%}
{% block CSS %}
    <link href="{{static_url("css/index.css")}}" rel="stylesheet" />
{% end %}

{% block RenderBody %}
    <h1>Index</h1>

    <ul>
    {%  for item in li %}
        <li>{{item}}</li>
    {% end %}
    </ul>

{% end %}

{% block JavaScript %}
    
{% end %}

导入

header.html

<div>
    <ul>
        <li>1024</li>
        <li>42区</li>
    </ul>
</div>

index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>老男孩</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div class="pg-header">
        {% include 'header.html' %}
    </div>
    
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>

模板语言自定义函数:ui_methods和ui_modules

定义方式

ui_methods定义方式,比较简单,直接是一个函数,需要注意的是:函数参数需要导入self参数

def tab(self):
    return 'UIMethod'
    
def fafafa(self,a):
    return "10"    

ui_modules 定义基本是一个类,更重要的是:ui_modules能定义html中的js css 和html语言

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escape

class custom(UIModule):

    def render(self, *args, **kwargs):
        return escape.xhtml_escape('<h1>wupeiqi</h1>')
        #return escape.xhtml_escape('<h1>wupeiqi</h1>')
        
class custom(UIModule):

    def javascript_files(self):
        return ['http://assdfasdfasdf.js','http://11111.js',]

    def embedded_javascript(self):
        return "function f1(){alert(123);}"

    def css_files(self):
        pass

    def embedded_css(self):
        pass

    def render(self, *args, **kwargs):
        return "<h1>CCCC</h1>"    

settings中注册

#!/usr/bin/env python
# -*- coding:utf-8 -*-

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()

模板中的使用方式

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
    {% module custom(123) %}
    {{ tab() }}
</body>

静态文件以及路径

对于静态文件,我们需要在settings里设置static_path,也可以设置static_url_prefix(设置模板中URL前段使用时的前缀访问地址).

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import tornado.ioloop
import tornado.web
 
 
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('home/index.html')
 
settings = {
    'template_path': 'template',
    #设置静态文件目录
    'static_path': 'static',
    #设置URL前缀地址
    'static_url_prefix': '/static/',
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(80)
    tornado.ioloop.IOLoop.instance().start()

模板中使用方式:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
</body>
</html>

静态文件缓存的实现方式:

def get_content_version(cls, abspath):
        """Returns a version string for the resource at the given path.

        This class method may be overridden by subclasses.  The
        default implementation is a hash of the file's contents.

        .. versionadded:: 3.1
        """
        data = cls.get_content(abspath)
        hasher = hashlib.md5()
        if isinstance(data, bytes):
            hasher.update(data)
        else:
            for chunk in data:
                hasher.update(chunk)
        return hasher.hexdigest()

tornado中可以对cookie进行操作,并且还可以对cookie进行签名以放置伪造.

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!")

加密cookie(加密)

Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。

Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

使用方式:

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=")

内部算法(先copy出来,以后再看):

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过程:

  1. 将值进行64位加密
  2. 对除值以外的内容进行签名,哈希算法
  3. 拼接签名+加密值

读cookie过程:

  1. 读取签名+加密值
  2. 对签名进行验证
  3. base64解密,获取值内容

所以,利很多API接口验证机制和安全cookie实现机制同上面的原理:

api接口和安全cookie

基于Cookie实现用户验证-Demo:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
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', '武沛齐')
            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:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
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', '武沛齐')
            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()

JavaScript操作cookie

由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie。

设置cookie,指定秒数过期:

function setCookie(name,value,expires){
    var temp = [];
    var current_date = new Date();
    current_date.setSeconds(current_date.getSeconds() + 5);
    document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
}

跨站请求伪造CSRF

tornado中的跨站请求伪造和Django中的和相似

配置:

settings = {
    "xsrf_cookies": True,
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)

使用:普通form表单

<form action="/new_message" method="post">
  {{ xsrf_form_html() }}
  <input type="text" name="message"/>
  <input type="submit" value="Post"/>
</form>

使用:ajax

function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

jQuery.postJSON = function(url, args, callback) {
    args._xsrf = getCookie("_xsrf");
    $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
        success: function(response) {
        callback(eval("(" + response + ")"));
    }});
};

异步非阻塞

tornado天生支持异步非阻塞,这也是最大的特点,基本流程如下:

  1. Future容器,默认夯住请求,连接不断开,等待用户向Future中发送数据
  2. 夯住:http未断开;tornado可以单线程去操作请求其他请求(即协程模式)
  3. httpclient类库
  4. Tornado永远不会自己关闭连接,需要显式的self.finish()关闭

基本使用

装饰器+Future实现tornado的异步非阻塞

class AsyncHandler(tornado.web.RequestHandler):
 
    @gen.coroutine
    def get(self):
        future = Future()
        future.add_done_callback(self.doing)
        yield future
        # 或
        # tornado.ioloop.IOLoop.current().add_future(future,self.doing)
        # yield future
 
    def doing(self,*args, **kwargs):
        self.write('async')
        self.finish()

当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。

异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。

注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。

同步阻塞和异步阻塞的对比

同步阻塞:

class SyncHandler(tornado.web.RequestHandler):

    def get(self):
        self.doing()
        self.write('sync')

    def doing(self):
        time.sleep(10)

异步阻塞:

class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        future = Future()
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
        yield future


    def doing(self, *args, **kwargs):
        self.write('async')
        self.finish()

httpclient类库

Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。

class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        from tornado import httpclient
 
        http = httpclient.AsyncHTTPClient()
        yield http.fetch("http://www.google.com", self.endding)
 
 
    def endding(self, response):
        print(len(response.body))
        self.write('ok')
        self.finish()

另外,新的tornado.gen.coroutine装饰器, coroutine是3.0之后新增的装饰器,以后就不需要使用@tornado.web.asynchronous了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值