Tornado 基本操作
讲师的博客:
白话tornado源码系列5篇,主要是源码剖析暂时不需要知道那么多。只要看下第一篇就好:
https://www.cnblogs.com/wupeiqi/tag/Tornado/
Web框架之Tornado:
https://www.cnblogs.com/wupeiqi/p/5702910.html
Hello World
经典的 hello world 示例:
import tornado.web
# 视图
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello World.")
# 路由
application = tornado.web.Application([
(r"/", MainHandler),
(r"/hello", MainHandler),
])
if __name__ == '__main__':
import tornado.ioloop
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
整个过程其实就是在创建一个socket服务端并监听8000端口。当请求到来时,根据请求中的url和请求方式(post、get或put等)来指定相应的类中的方法来处理本次请求。在上述示例中 url 在路由系统匹配到时,则服务器会给浏览器返回 Hello World ,否则返回 404: Not Found(tornado内部定义的值), 即完成一次http请求和响应。
模板引擎
Tornao中的模板语言和django中类似。模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。
不过还是有区别的。Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的。例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }} 。
控制语句和对应的 Python 语句的格式基本完全相同。支持 if、for、while 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块的代码文档中有着详细的描述。在使用模板前需要在setting中设置模板路径:"template_path" : "tpl"
使用模板引擎的简单示例,后端代码:
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", k1='v1', k2='v2') # k1和k2是传给模板引擎处理的内容
application = tornado.web.Application([
(r"/index", IndexHandler),
])
if __name__ == '__main__':
import tornado.ioloop
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
前端代码,模板语言的使用:
Hello World
{{ k1 }}
{% if k2 == 'v2' %}
k2 == v2
{% else %}}
k2 != v2
{% end %}
加载配置
上面的前端代码,最好是统一保存在某个目录里,比如新建个tpl目录来存放。把html文件移过去之后,现在render()方法就找不到这个文件了。当然可以改一下参数,把目录名加进去。不过推荐的做法是把tpl目录加到配置里去,对上面的代码进去修改,加入配置信息:
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", k1='v1', k2='v2')
# 配置就是个key-value的字段
settings = {
'template_path': 'tpl'
}
application = tornado.web.Application([
(r"/index", IndexHandler),
], **settings) # application加载配置信息
POST
先准备好如下的页面,在输入框里填入要搜索的关键字,提交后就跳转到搜索引擎搜索的结果:
后端的代码:
import tornado.web
class SearchHandler(tornado.web.RequestHandler):
def get(self):
self.render("baidu.html")
def post(self):
wd = self.get_argument('wd')
print(wd)
self.redirect('https://www.baidu.com/s?wd=%s' % wd)
settings = {
'template_path': 'tpl'
}
application = tornado.web.Application([
(r"/baidu", SearchHandler),
], **settings)
if __name__ == '__main__':
import tornado.ioloop
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
上面的示例,post请求最后是用redirect()返回的,这个是页面的跳转。
获取提交的参数的方法有这些:
class LoginHandler(tornado.web.RequestHandler):
def post(self):
# 获取URL中以GET形式传递的数据
self.get_query_argument()
self.get_query_arguments()
# 获取请求体中以POST形式传递的数据
self.get_body_argument()
self.get_body_arguments()
# 从上面2个里都尝试获取
self.get_argument()
self.get_arguments()
静态文件(图片)
静态文件是给用户直接下载的,所以应该单独存放,并且在配置里注册对应的目录。配置的写法:
settings = {
'template_path': 'tpl', # 模板
'static_path': 'imgs', # 静态文件
}
现在可以根据配置里的名称去创建一个新的文件夹 static 用来存放静态文件。然后放张图片进去。
这里故意不用static作为静态文件文件夹的名称,这里只是注册文件夹,但是前端引用的时候,无论你的静态文件放在那里,都是用 static/文件名称 。
加一个img标签到html里,然后验证一下效果。注意src里用的是static,而不是文件夹真正的名称:
这里前端引用的是必须用static,不过这个名字也是可以自定义的:
settings = {
'template_path': 'tpl', # 模板
'static_path': 'imgs', # 静态文件
'static_url_prefix': '/statics/', # 注意两边都要有斜杠/
}
其他操作
self.request.cookies
: 获取cookiesself.set_cookie()
: 设置cookieself.request.headers
: 获取请求头self.set_header()
: 设置响应头,如果出现同一个响应头,会覆盖self.add_header()
: 设置响应头,如果出现同一个响应头,则追加
Tornado 没有提供 session ,所以要用的话,得另外写。同样的,缓存也没有。
进阶操作
最基本的就是上面那些了,这里再补充一点别的。
自定义UIMethod以及UIModule
这个就是模板引擎里的自定义函数。
UIMethod 自定义的是个函数,UIModule 自定义的是个类。
定义
把自定义的函数和自定义的类单独写在文件里:
# ui_methods.py
def test1(self): # 这里的self不能去掉
return "TEST1"
def test2(self):
return "TEST2"
# ui_module.py
from tornado.web import UIModule
from tornado import escape
class Test(UIModule):
def render(self, *args, **kwargs):
return escape.xhtml_escape('UI Module TEST
')
注册
写一个完整的服务,这里加上注册的代码。先导入上面的文件,然后分别在settings里注册:
import tornado.web
import ui_methods as mt
import ui_modules as md
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('ui.html')
settings = {
'template_path': 'tpl', # 模板
'static_path': 'static', # 静态文件,这里不重要
'static_url_prefix': '/statics/', # 注意两边都要有斜杠/
'ui_methods': mt,
'ui_modules': md,
}
application = tornado.web.Application([
(r"/ui", MainHandler),
], **settings)
if __name__ == '__main__':
import tornado.ioloop
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
使用
这里只需要看明白前端调用的方法就可以了
UI Method
{{ test1() }}
{{ test2() }}
UI Module
{% module Test() %}
UIModule里的方法
render 方法返回的内容就是调用模板的位置显示的内容:
class Test(UIModule):
def javascript_files(self):
pass
def embedded_javascript(self):
pass
def css_files(self):
pass
def embedded_css(self):
pass
def render(self, *args, **kwargs):
return escape.xhtml_escape('UI Module TEST
')
javascript的方法会在body的尾部插入script标签,插入js代码
css的方法则会在head里插入style标签,设置css
files就是直接引入文件,进行设置
embedded就是插入返回的字符串作为设置
CSRF
首先在settings里启用csrf:
settings = {
"xsrf_cookies": True,
}
在 form 中使用
{{ xsrf_form_html() }} 能够输出完整的input标签,但是直接用回被解析为字符串,带着标签的信息作为字符串显示出来。所以上面用的是 {% raw xsrf_form_html() %} 。
在 Ajax 中使用
Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求:
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 + ")"));
}});
};
上传文件
先准备一个form表单上传文件的html页面:
上传文件
接收上传文件:
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
file_metas = self.request.files["fff"]
# print(file_metas)
for meta in file_metas:
file_name = meta['filename']
with open(file_name,'wb') as up:
up.write(meta['body'])
settings = {
'template_path': 'template',
}
application = tornado.web.Application([
(r"/index", MainHandler),
], **settings)
if __name__ == '__main__':
import tornado.ioloop
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()
上传文件还可以用Ajax,另外还有借助iframe标签实现的伪Ajax的实现,略...
异步非阻塞
异步非阻塞IO,高并发高性能是tornado的特点,所以这小节很重要。但是具体内容也没搞明白,只能尽量先记一些。
首先要引入下面的2个模块:
from tornado import gen
from tornado.concurrent import Future
class AsyncHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
future = Future()
future.add_done_callback(self.doing)
yield future
def doing(self,*args, **kwargs):
self.write('async')
self.finish()
当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。
这里发送请求后,永远也不会返回,就是按上面说的Tornado一直在等待。等待调用了 future.set_result(result) 这个方法。之后就会调用回调函数,而set_result方法里传递进去的参数,可以通过 future.result() 获取到。大致就是这么的用法,但是没有个使用示例有点不好理解。
Future类
Future类位于tornado源码的concurrent模块中。下面是Future类里的一部分代码作为分析之用:
class Future(object):
def done(self):
return self._done
def result(self, timeout=None):
self._clear_tb_log()
if self._result is not None:
return self._result
if self._exc_info is not None:
raise_exc_info(self._exc_info)
self._check_done()
return self._result
def add_done_callback(self, fn):
if self._done:
fn(self)
else:
self._callbacks.append(fn)
def set_result(self, result):
self._result = result
self._set_done()
def _set_done(self):
self._done = True
for cb in self._callbacks:
try:
cb(self)
except Exception:
app_log.exception('exception calling callback %r for %r',
cb, self)
self._callbacks = None
Future类重要成员函数:
- def done(self) : Future的_result成员是否被设置
- def result(self, timeout=None) : 获取Future对象的结果
- def add_done_callback(self, fn) : 添加一个回调函数fn给Future对象。如果这个Future对象已经done,则直接执行fn,否则将fn加入到Future类的一个成员列表中保存。
- def_set_done(self) : 一个内部函数,主要是遍历列表,逐个调用列表中的callback函数,也就是前面 add_done_calback 加如来的。
- def set_result(self, result) : 给Future对象设置result,并且调用_set_done。也就是说,当Future对象获得result后,所有add_done_callback加入的回调函数就会执行。
这里最终就是希望 future 调用 set_result ,然后就是执行回调函数。
自定义异步非阻塞Web框架
这节主要是想以源码的方式展示分析tornado是怎么实现异步非阻塞的。代码应该不是超的源码,只是借鉴了思路,做了很多简化。
下面是实现异步非阻塞的代码,主要是 select+socket :
https://www.cnblogs.com/wupeiqi/p/6536518.html
什么场景考虑使用Tornado
复杂的应用还是用django来开发。
如果要开发一个API的功能,或者其他的简单的工具、应用,也不用操作数据库。就可以用tornado或者是其他简单的框架。就不需要用django了。
可以选择的简单的框架还有Flask。