概述
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 %}做标记,还可以通过extends
和block
实现模板的继承和嵌套.
我们可以总结下tornado和django中的模板语言区别:
- 控制语句的结束标记:tornado:
{% end %}
,django:{% endfor %}
等. - 索引取值: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()
cookie
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过程:
- 将值进行64位加密
- 对除值以外的内容进行签名,哈希算法
- 拼接签名+加密值
读cookie过程:
- 读取签名+加密值
- 对签名进行验证
- 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天生支持异步非阻塞,这也是最大的特点,基本流程如下:
- Future容器,默认夯住请求,连接不断开,等待用户向Future中发送数据
- 夯住:http未断开;tornado可以单线程去操作请求其他请求(即协程模式)
- httpclient类库
- 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
了.