Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。
tornado技术文档:Tornado文档中文翻译版
ubuntu下安装tornado: pip install tarnado (其他安装方式自行百度)
Tornado 主要服务分为 服务器模块(httpserver) IO接口模块(ioloop) web模块(web)
TORNADO 项目的简单创建
初步使用这三大模块来建立一个简易的 web 服务
1 #导入相关服务模块 2 3 import tornado.httpserver 4 5 import tornado.ioloop 6 7 import tornado.web 8 9 10 11 class IndexHandler(tornado.web.RequestHandler): 12 def get(self): 13 greeting = self.get_argument('greeting', 'Hello') 14 self.write(greeting + ', friendly user!') 15 16 17 if __name__ == "__main__": 18 19 app = tornado.web.Application( 20 handlers=[(r"/", IndexHandler)] 21 ) 22 http_server = tornado.httpserver.HTTPServer(app) 23 http_server.listen(8000) 24 tornado.ioloop.IOLoop.current().start()
以上就是一个简单的web服务,通过访问该 ip 的8000端口 就能访问到设置的相关信息或界面!
我们来认识一下上面的相关代码:
类 IndexHandler 是一个句柄:描述控制器操作过程的一个动作
在app中设置路由及其他一些操作,参数handlers非常重要,它应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类(即句柄)。你可以按你的需要指定任意多个。
通过 tornado.httpserver.HTTPServer 绑定 Application 对象
通过 tornado.httpserver.HTTPServer().listen() 设置监听端口
通过 tornado.ioloop.IOLoop.current().start() 开始循环监听端口
TORNADO 框架的初步应用
下面开始真正探讨 Tornado 的一些模块与功能:
全局变量的定义:
1 import tornado.options 2 3 tornado.options.define(“port” , default = 8888,type = int, help = “程序启动端口”) 4 5 tornado.options.define(“msg”,default = “这是一条测试信息”)
以上就是全局变量的定义方法,我们定义了一个端口与一个“msg”测试信息。
使用全局变量:我们已经定义好了一个端口变量,现在我们来使用它,例如;
1 port = tornado.options.options.port 2 3 http_server.listen(port)
接受命令行参数:
1 tornado.options.parse_command_line()
加载配置文件到程序中:
如:创建一个配置文件,文件名:config 文件内容:port = 8000
加载文件: tornado.options.parse_config_file(“./config”)
配置文件中的变量一定确保已经被 define 定义过!
指定网页模板目录和指定静态文件目录:
1 import os 2 3 BASE_DIR = os.path.dirname(__file__) 4 5 template_path = os.path.join(BASE_DIR,“templates”) 6 7 static_path = os.path.join(BASE_DIR,“static”)
定义路径的语句app中,模板与静态文件为同级目录下的 templates 与 static 文件夹
1 app = tornado.web.Application( 2 handlers=[(r"/", IndexHandler)], debug = True #开启开发调试模式template_path = os.path.join(BASE_DIR,“templates”) 3 static_path = os.path.join(BASE_DIR,“static”) 4 )
使用static_url生成静态URL:
1 <link rel="stylesheet" href="{{ static_url("style.css") }}"> 2 #生成了类似下面的代码 3 <link rel="stylesheet" href="/static/style.css?v=ab12">
数据的传输:
get方式接收 –>
单个数据:get_query_argument() 多个数据:get_query_arguments()
post方式接收 –>
单个数据:get_body_argument() 多个数据:get_body_arguments()
未指定方式接收 –>
单个数据:get_argument() 多个数据:get_arguments()
HTTP状态码:
404 Not Found
Tornado会在HTTP请求的路径无法匹配任何RequestHandler类相对应的模式时返回404(Not Found)响应码。
400 Bad Request
如果你调用了一个没有默认值的get_argument函数,并且没有发现给定名称的参数,Tornado将自动返回一个400(Bad Request)响应码。
405 Method Not Allowed
如果传入的请求使用了RequestHandler中没有定义的HTTP方法(比如,一个POST请求,但是处理函数中只有定义了get方法),Tornado将返回一个405(Methos Not Allowed)响应码。
500 Internal Server Error
当程序遇到任何不能让其退出的错误时,Tornado将返回500(Internal Server Error)响应码。你代码中任何没有捕获的异常也会导致500响应码。
200 OK
如果响应成功,并且没有其他返回码被设置,Tornado将默认返回一个200(OK)响应码。
使用自己的方法代替默认的错误响应,你可以重写write_error方法在你的RequestHandler类中。
1 def write_error(self, status_code, **kwargs): 2 self.write("Gosh darnit, user! You caused a %d error." % status_code)
cookies设置:
Cookie 是由 Web 服务器保存在用户浏览器(客户端)上的小文本文件,它可以包含有关用户的信息。无论何时用户链接到服务器,Web 站点都可以访问 Cookie 信息 。
会话跟踪是一种灵活、轻便的机制,它使Web上的状态编程变为可能。
HTTP是一种无状态协议,每当用户发出请求时,服务器就会做出响应,客户端与服务器之间的联系是离散的、非连续的。
当用户在同一网站的多个页面之间转换时,根本无法确定是否是同一个客户,会话跟踪技术就可以解决这个问题。
当一个客户在多个页面间切换时,服务器会保存该用户的信息。
有四种方法可以实现会话跟踪技术:URL重写、隐藏表单域、Cookie、Session。
存入数据:
1 self.set_cookie(“msg”:”这是一条测试信息!”) #一般安全cookie 2 3 ———————————————————————————————————————— 4 5 self.set_secure_cookie(“msg”:”这是一条安全cookie测试信息!”) #安全cookie 6 7 #!!!设置安全cookie前必须具有混淆码 设置方式如下 8 9 # 1.生成一个混淆码 10 11 # import uuid,base64 12 13 # code = base64.b64encode(uuid.uuid4().bytes+uuid.uuid4().bytes) 14 15 # 2.将混淆码加入cookie设置(在app里添加以下代码) 16 17 # cookie_secret = “%s”%code 18 19 ———————————————————————————————————————— 20 21 #可以向set_cookie和set_secure_cookie方法传递关键字参数。比如,一个安全的HTTP-only cookie(上面的安全cookie其实是签名cookie)可以调用self.set_cookie('foo', 'bar', httponly=True, secure=True)发送。
设置过期时间的 cookie 为永久 cookie , 没有设置的为临时 cookie,如:
self.set_cookie(“msg”:”这是一条测试信息!”,expires_day = 1)
读取数据:
1 self.get_cookie(“msg”) 2 3 self.get_secure_cookie()
cookie认证装饰器:
- 引入装饰器 -> –> from tornado.web import authenticated
- 在需要装饰的方法前加 -> –> @authenticated
- 在 app 中添加认证失败的跳转地址 -> –> login_url = “/login”
- 重写装饰器中获取数据函数
1 def get_current_user(self): 2 3 status = self.get_secure_cookies(“login_user”) 4 5 if status: 6 7 return True 8 9 return False
跨域请求伪造防范:
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
任何Web应用所面临的一个主要安全漏洞是跨站请求伪造,通常被简写为CSRF或XSRF,发音为"sea surf"。这个漏洞利用了浏览器的一个允许恶意攻击者在受害者网站注入脚本使未授权请求代表一个已登录用户的安全漏洞。
app 中添加 xsrf——cookies = True
html页面提交表单中加入 {% module xsrf_form_html( ) %}
XSRF令牌和AJAX请求:
AJAX请求也需要一个_xsrf参数,但不是必须显式地在渲染页面时包含一个_xsrf值,而是通过脚本在客户端查询浏览器获得cookie值。下面的两个函数透明地添加令牌值给AJAX POST请求。第一个函数通过名字获取cookie,而第二个函数是一个添加_xsrf参数到传递给postJSON函数数据对象的便捷函数。
1 function getCookie(name) { 2 var c = document.cookie.match("\b" + name + "=([^;]*)\b"); 3 return c ? c[1] : undefined; 4 } 5 6 jQuery.postJSON = function(url, data, callback) { 7 data._xsrf = getCookie("_xsrf"); 8 jQuery.ajax({ 9 url: url, 10 data: jQuery.param(data), 11 dataType: "json", 12 type: "POST", 13 success: callback 14 }); 15 }
同步与异步:
大部分Web应用(包括我们之前的例子)都是阻塞性质的,也就是说当一个请求被处理时,这个进程就会被挂起直至请求完成。在大多数情况下,Tornado处理的Web请求完成得足够快使得这个问题并不需要被关注。然而,对于那些需要一些时间来完成的操作(像大数据库的请求或外部API),这意味着应用程序被有效的锁定直至处理结束,Tornado给了我们更好的方法来处理这种情况。应用程序在等待第一个处理完成的过程中,让I/O循环打开以便服务于其他客户端,直到处理完成时启动一个请求并给予反馈,而不再是等待请求完成的过程中挂起进程。
这里用一个简单的例子来实现同步与异步:
Eg:向淘宝发起搜索请求:https://suggest.taobao.com/sug?code=utf-8&q=(关键字)
先写一个同步的简例
1 import tornado.web 2 3 import tornado.httpserver 4 5 import tornado.httpclient 6 7 import tornado.ioloop 8 9 import urllib,json 10 11 12 13 class IndexHandler(tornado.web.RequestHandler): 14 15 def get(self): 16 17 query = “iphonex” 18 19 client = tornado.httpclient.HTTPClient() 20 21 response = client.fetch(“https://suggest.taobao.com/sug?code=utf-8&”+urllib.parse.urlencode({“q”:query})) 22 23 24 25 def deal_response(self,response): 26 27 content = response.body 28 29 self.write(response.body) 30 31 32 33 if __name__ == “__main__”: 34 35 app = tornado.web.Application(handlers = [(r”/”,IndexHandler)]) 36 37 app.listen(8000) 38 39 tornado.ioloop.IOLoop.current().start()
再用异步的方式写一下:
1 import tornado.web 2 3 import tornado.httpserver 4 5 import tornado.httpclient 6 7 import tornado.ioloop 8 9 import urllib,json 10 11 from tornado.httpclient import AsyncHTTPClient 12 13 14 15 class IndexHandler(tornado.web.RequestHandler): 16 17 #异步请求装饰器,如果不添加,get函数会自动生成一个return,程序会报错 18 19 @tornado.web.asynchronous 20 21 def get(self): 22 23 query = “iphonex” 24 25 client = tornado.httpclient.HTTPClient() 26 27 response = client.fetch(“https://suggest.taobao.com/sug?code=utf-8&”+urllib.parse.urlencode({“q”:query}),callback = self.deal_response) 28 29 self.write(response.body) 30 31 self.finish() #回调函数结束时需要 32 33 34 35 if __name__ == “__main__”: 36 37 app = tornado.web.Application(handlers = [(r”/”,IndexHandler)]) 38 39 app.listen(8000) 40 41 tornado.ioloop.IOLoop.current().start()
在运行程序过程中,从浏览器上感觉不出同步异步有什么不同,可以用 siege 压力测试一下:
(siege 安装方式:sudo apt-get install siege)
上图左边是同步测试,右边是异步测试(连接量10,时间10s)。可以看出相同环境下,异步的连接处理数是同步的六倍,异步平均每秒18.32,同步平均每秒只有3.58
模板语法
填充表达式:
1 >>> from tornado.template import Template 2 >>> print Template("{{ 1+1 }}").generate() 3 2 4 >>> print Template("{{ 'scrambled eggs'[-4:] }}").generate() 5 eggs 6 >>> print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate() 7 0, 1, 4, 9, 16, 25, 36, 49, 64, 81
控制流语句:
在Tornado模板中使用Python条件和循环语句。控制语句以{%和%}包围
{% if page is None %} 或 {% if len(entries) == 3 %}
控制语句的大部分就像对应的Python语句一样工作,支持if、for、while和try。在这些情况下,语句块以{%开始,并以%}结束。
1 <h1>{{ header }}</h1> 2 <ul> 3 {% for book in books %} 4 <li>{{ book }}</li> 5 {% end %} 6 </ul>
模板函数:
escape(s)
替换字符串s中的&、<、>为他们对应的HTML字符。
url_escape(s)
使用urllib.quote_plus替换字符串s中的字符为URL编码形式。
json_encode(val)
将val编码成JSON格式。(在系统底层,这是一个对json库的dumps函数的调用。查阅相关的文档以获得更多关于该函数接收和返回参数的信息。)
squeeze(s)
过滤字符串s,把连续的多个空白字符替换成一个空格。
模板扩展
块和替换:
为了扩展一个已经存在的模板,你只需要在新的模板文件的顶部放上一句{% extends "filename.html" %}
比如在一个新模板中扩展一个父模板(main.html):
{% extends "main.html" %}
块基础:
为了使用一个能够根据不同页覆写的动态header块,在父模板main.html中添加如下代码:
1 <header> 2 {% block header %}{% end %} 3 </header>
然后,为了在子模板index.html中覆写{% block header %}{% end %}
部分,你可以使用块的名字引用,并把任何你想要的内容放到其中。
1 {% block header %}{% end %} 2 3 {% block header %} 4 <h1>Hello world!</h1> 5 {% end %}
任何继承这个模板的文件都可以包含它自己的{% block header %}
和{% end %}
,然后把一些不同的东西加进去。
可以为每个页面使用多个块,此时像header和footer这样的动态元素将会被包含在同一个流程中。
父模板main.html:
1 <html> 2 <body> 3 <header> 4 {% block header %}{% end %} 5 </header> 6 <content> 7 {% block body %}{% end %} 8 </content> 9 <footer> 10 {% block footer %}{% end %} 11 </footer> 12 </body> 13 </html>
当我们扩展父模板main.html时,可以在子模板index.html中引用这些块。
1 {% extends "main.html" %} 2 3 {% block header %} 4 <h1>{{ header_text }}</h1> 5 {% end %} 6 7 {% block body %} 8 <p>Hello from the child template!</p> 9 {% end %} 10 11 {% block footer %} 12 <p>{{ footer_text }}</p> 13 {% end %}
自动转义:
Tornado默认会自动转义模板中的内容,把标签转换为相应的HTML实体。
当不需要转义时,你可以禁用自动转义,一种方法是在Application构造函数中传递autoescape=None,另一种方法是在每页的基础上修改自动转义行为,如下所示:
1 {% autoescape None %} 2 {{ mailLink }}
这些autoescape块不需要结束标签,并且可以设置xhtml_escape来开启自动转义(默认行为),或None来关闭。
然而,在理想的情况下,你希望保持自动转义开启以便继续防护你的网站。因此,你可以使用{% raw %}指令来输出不转义的内容。
{% raw mailLink %}
需要特别注意的是,当你使用诸如Tornado的linkify()和xsrf_form_html()函数时,自动转义的设置被改变了。所以如果你希望在前面代码的footer中使用linkify()来包含链接,你可以使用一个{% raw %}块:
1 {% block footer %} 2 <p> 3 For more information about our selection, hours or events, please email us at 4 <a href="mailto:[email protected]">[email protected]</a>. 5 </p> 6 7 <p class="small"> 8 Follow us on Facebook at 9 {% raw linkify("https://fb.me/burtsbooks", extra_params='ref=website') %}. 10 </p> 11 {% end %}
这样,你可以既利用linkify()简记的好处,又可以保持在其他地方自动转义的好处。
嵌入JavaScript和CSS:
Tornado允许你使用embedded_css和embedded_javascript方法嵌入其他的CSS和JavaScript文件。
例如,你想在调用模块时给DOM添加一行文字,你可以通过从模块中嵌入JavaScript来做到:
1 class BookModule(tornado.web.UIModule): 2 def render(self, book): 3 return self.render_string( 4 "modules/book.html", 5 book=book, 6 ) 7 8 def embedded_javascript(self): 9 return "document.write("hi!")"
当调用模块时,document.write("hi!")将被<script>包围,并被插入到<body>的闭标签中:
1 <script type="text/javascript"> 2 //<![CDATA[ 3 document.write("hi!") 4 //]]> 5 </script>
你也可以把只在这些模块被调用时加载的额外的CSS规则放进来:
1 def embedded_css(self): 2 return ".book {background-color:#F5F5F5}"
在这种情况下,.book {background-color:#555}
这条CSS规则被包裹在<style>中,并被直接添加到<head>的闭标签之前。
1 <style type="text/css"> 2 .book {background-color:#F5F5F5} 3 </style>
甚至可以简单地使用html_body()来在闭合的</body>标签前添加完整的HTML标记:
1 def html_body(self): 2 return "<script>document.write("Hello!")</script>"
为了更严谨的包含(以及更整洁的代码!),添加样式表和脚本文件会显得更好。他们的工作方式基本相同,所以你可以使用javascript_files()和css_files()来包含完整的文件,不论是本地的还是外部的。
1 #添加个本地的css文件 2 def css_files(self): 3 return "/static/css/newreleases.css" 4 5 #添加一个外部的JavaScript文件: 6 def javascript_files(self): 7 return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"
因为模块的内嵌JavaScript和内嵌HTML函数的目标都是紧邻</body>标签,html_body()、javascript_files()和embedded_javascript()都会将内容渲染后插到页面底部,那么它们出现的顺序正好是你指定它们的顺序的倒序。
1 class SampleModule(tornado.web.UIModule): 2 def render(self, sample): 3 return self.render_string( 4 "modules/sample.html", 5 sample=sample 6 ) 7 8 def html_body(self): 9 return "<div class="addition"><p>html_body()</p></div>" 10 11 def embedded_javascript(self): 12 return "document.write("<p>embedded_javascript()</p>")" 13 14 def embedded_css(self): 15 return ".addition {color: #A1CAF1}" 16 17 def css_files(self): 18 return "/static/css/sample.css" 19 20 def javascript_files(self): 21 return "/static/js/sample.js"
html_body()最先被编写,它紧挨着出现在</body>标签的上面。embedded_javascript()接着被渲染,最后是javascript_files()。
不能包括一个需要其他地方东西的方法(比如依赖其他文件的JavaScript函数),因为此时他们可能会按照和你期望不同的顺序进行渲染。
WebSockets:
WebSockets是HTML5规范中新提出的客户-服务器通讯协议。WebSocket协议提供了在客户端和服务器间持久连接的双向通信。协议本身使用新的ws://URL格式,但它是在标准HTTP上实现的。通过使用HTTP和HTTPS端口,它避免了从Web代理后的网络连接站点时引入的各种问题。HTML5规范不只描述了协议本身,还描述了使用WebSockets编写客户端代码所需要的浏览器API。
Tornado的WebSocket模块:
Tornado在websocket模块中提供了一个WebSocketHandler类。这个类提供了和已连接的客户端通信的WebSocket事件和方法的钩子。当一个新的WebSocket连接打开时,open方法被调用,而on_message和on_close方法分别在连接接收到新的消息和客户端关闭时被调用。
1 class IndexHandler(tornado.websocket.WebSocketHandler): 2 def open(self): 3 self.write_message('connected!') 4 5 def on_message(self, message): 6 self.write_message(message)