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()
前端代码, 模板语言的使用:
- <body>
- <h1>Hello World</h1>
- <h3>{{ k1 }}</h3>
- {% if k2 == 'v2' %}
- <h3>k2 == v2</h3>
- {% else %}}
- <h2>k2 != v2</h2>
- {% end %}
- </body>
加载配置
上面的前端代码, 最好是统一保存在某个目录里, 比如新建个 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
先准备好如下的页面, 在输入框里填入要搜索的关键字, 提交后就跳转到搜索引擎搜索的结果:
- <body>
- <form method="POST" action="/baidu">
- <input type="text" name="wd" />
- <input type="submit" value="百度一下" />
- </form>
- </body>
后端的代码:
- 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, 而不是文件夹真正的名称:
<img src="static/test.jpg" />
这里前端引用的是必须用 static, 不过这个名字也是可以自定义的:
- settings = {
- 'template_path': 'tpl', # 模板
- 'static_path': 'imgs', # 静态文件
- 'static_url_prefix': '/statics/', # 注意两边都要有斜杠 /
- }
其他操作
self.request.cookies : 获取 cookies
self.set_cookie() : 设置 cookie
self.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('<h3>UI Module TEST</h3>')
注册
写一个完整的服务, 这里加上注册的代码. 先导入上面的文件, 然后分别在 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()
使用
这里只需要看明白前端调用的方法就可以了
- <body>
- <h1>
- UI Method
- </h1>
- <p>
- {{ test1() }}
- </p>
- <p>
- {{ test2() }}
- </p>
- <h1>
- UI Module
- </h1>
- {% module Test() %}
- </body>
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('<h3>UI Module TEST</h3>')
JavaScript 的方法会在 body 的尾部插入 script 标签, 插入 JS 代码
CSS 的方法则会在 head 里插入 style 标签, 设置 CSS
files 就是直接引入文件, 进行设置
embedded 就是插入返回的字符串作为设置
CSRF
首先在 settings 里启用 csrf:
- settings = {
- "xsrf_cookies": True,
- }
在 form 中使用
- <form action="/new_message" method="post">
- {% raw xsrf_form_html() %}
- <input type="text" name="message"/>
- <input type="submit" value="提交"/>
- </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 页面:
- <HTML>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title > 上传文件</title>
- </head>
- <body>
- <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data">
- <input name="fff" id="my_file" type="file" />
- <input type="submit" value="提交" />
- </form>
- </body>
- </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.
来源: http://www.bubuko.com/infodetail-2874604.html