Python Tornado简介
Posted Dandy Zhang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python Tornado简介相关的知识,希望对你有一定的参考价值。
简介
Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。
Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。
Tornado安装
pip3 install tornado
初识Tornado
首先打开pycharm,新建一个干净的project
新建一个py文件:
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): # 类似于Django里面的CBV def get(self): # get方法 self.write("Hello, world") application = tornado.web.Application([ # 类似于Django里面的路由系统 (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() # 启动
运行文件并打开浏览器访问网址:
运行该脚本,依次执行:
- 创建一个Application对象,并把一个正则表达式\'/\'和类名MainHandler传入构造函数:tornado.web.Application(...)
- 执行Application对象的listen(...)方法,即:application.listen(8888)
- 执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()
整个过程其实就是在创建一个socket服务端并监听8888端口,当请求到来时,根据请求中的url和请求方式(post、get或put等)来指定相应的类中的方法来处理本次请求,在上述demo中只为url为http://127.0.0.1:8888/index的请求指定了处理类MainHandler(具体如何寻找见下文)。所以,在浏览器上访问:http://127.0.0.1:8888/index,则服务器给浏览器就会返回 Hello,world ,否则返回 404: Not Found(tornado内部定义的值), 即完成一次http请求和响应。
由上述分析,我们将整个Web框架分为两大部分:
- 待请求阶段,即:创建服务端socket并监听端口
- 处理请求阶段,即:当有客户端连接时,接受请求,并根据请求的不同做出相应的相应
经典的Login案例
首先,创建模版文件夹和模版文件:
login.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/login"> <input type="text" name="username" /> <input type="submit" value="提交" /> </form> </body> </html>
新建一个路由:
(r"/login", LoginHandler),
新建CBV:
class LoginHandler(tornado.web.RequestHandler): def get(self): self.render("login.html") def post(self, *args, **kwargs): # 获取url中以GET形式传递的数据 self.get_query_argument() self.get_query_arguments() # 去请求体中获取传递的数据 self.get_body_argument() self.get_body_arguments() # 去get&post双方去取数据 user = self.get_argument(\'username\') print(user) self.redirect(\'www.baidu.com\')
注册模版文件路径:
# 申明settings,字典格式 settings = { \'template_path\': \'tmp\' } # 注册进tornado内部 application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings)
整体app文件:
#!/usr/bin/env python3 # encoding: utf-8 # Author: Dandy import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") class LoginHandler(tornado.web.RequestHandler): def get(self): self.render("login.html") def post(self, *args, **kwargs): # 获取url中以GET形式传递的数据 self.get_query_argument() self.get_query_arguments() # 去请求体中获取传递的数据 self.get_body_argument() self.get_body_arguments() # 去get&post双方去取数据 user = self.get_argument(\'username\') print(user) self.redirect(\'www.baidu.com\') settings = { \'template_path\': \'tmp\' } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
此时便可执行
关于静态文件
首先添加静态文件夹,并放入一张图片
静态路径注册:
settings = { \'template_path\': \'tmp\', \'static_path\': \'statics\' }
login页面加入图片、注意以下路径写的是static,标准用法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/login"> <input type="text" name="username" /> <input type="submit" value="提交" /> </form> <img src="/static/炫雅.jpeg" /> </body> </html>
此时访问页面
那能不能不用static作为静态文件夹名称呢?
答案当然是可以的,只需要这样注册:
settings = { \'template_path\': \'tmp\', \'static_path\': \'statics\', \'static_url_prefix\': \'/jingtai/\', # 这里就是用户自定义的静态路径名称 }
再去修改login网页里的就好了。
详细介绍
上面简单的介绍了一下totornado的一些基本运用搭建。现在花点篇幅详细的解决各个模块的基本问题。
一、路由系统
路由系统从前面Django的学习过程中不难总结出来,其实就是url和视图函数的对应关系。当然啦,在Django里面,有FBV和CBV的概念;而在tornado每个url对应的函数均是一个类,即CBV。
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 HomeHandler(tornado.web.RequestHandler): def get(self): self.write("dandy") application = tornado.web.Application([ (r"/index", MainHandler), (r"/story/([0-9]+)", StoryHandler), ]) application.add_handlers(r"^a\\.com$", [ (r"/", HomeHandler), # 路由a.com:8888/ ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
Tornado中原生支持二级域名的路由,如:
二、模版引擎
Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。
Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {%
和 %}
包起来的 例如 {% if len(items) > 2 %}
。表达语句是使用 {{
和 }}
包起来的,例如 {{ items[0] }}
。
控制语句和对应的 Python 语句的格式基本完全相同。我们支持 if
、for
、while
和 try
,这些语句逻辑结束的位置需要用 {% end %}
做标记。还通过 extends
和 block
语句实现了模板继承。
注:在使用模板前需要在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], key1=\'value1\') settings = { \'template_path\': \'tmp\' } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href=\'{{static_url("css/common.css")}}\' rel="stylesheet" /> </head> <body> <h1>{{ key1 }}</h1> <ul> {% for item in list_info %} {% if item > 12 %} <li>{{ item }}</li> {% end %} {% end %} </ul> </body> </html>
类似于static_url的其他方法
在模板中默认提供了一些函数、字段、类以供模板使用: 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 的別名
2、母版
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>title</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> layout.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 %}
3、导入小标签
<div> <ul> <li>dandy</li> <li>durant</li> </ul> </div>
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title></title> </head> <body> <div class="pg-header"> {% include \'header.html\' %} </div> </body> </html>
4、模版函数扩展UIMethod&UIModule
a、申明
def tab(self): return \'UIMethod\'
from tornado.web import UIModule from tornado import escape class custom(UIModule): def render(self, *args, **kwargs): return escape.xhtml_escape(\'<h1>wuzdandz</h1>\')
b、注册
import uimodules as md import uimethods as mt settings = { \'template_path\': \'template\', \'static_path\': \'static\', \'ui_methods\': mt, \'ui_modules\': md, }
c、使用
<body> {% module custom(123) %} <br> {{ tab() }} </body>
注意一下这几个方法:
embedded_css/javascript 用来产生css或js语句的
javascript/css_files用来产生link指向文件
三、静态文件
对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。
settings = { \'template_path\': \'template\', \'static_path\': \'static\', \'static_url_prefix\': \'/static/\', } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings)
<img src="/static/炫雅.jpeg" /> <link href=\'{{static_url("commons.css")}}\' rel="stylesheet" />
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进行签名以放置伪造。
1、基本操作
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_cookie("username"): # 获取cookie self.set_cookie("username", "dandy") # 设置cookie self.write("Your cookie was not set yet!") else: self.write("Your cookie was set!")
2、加密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=")
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(Tornado-简介