Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 伺服器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過為了能有效利用非阻塞式伺服器環境,這個 Web 框架還包含了一些相關的有用工具 和優化。 Tornado 和現 ...
Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 伺服器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過為了能有效利用非阻塞式伺服器環境,這個 Web 框架還包含了一些相關的有用工具 和優化。
Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其 非阻塞的方式和對 epoll 的運用,Tornado 每秒可以處理數以千計的連接,這意味著對於實時 Web 服務來說,Tornado 是一個理想的 Web 框架。我們開發這個 Web 伺服器的主要目的就是為了處理 FriendFeed 的實時功能 ——在 FriendFeed 的應用里每一個活動用戶都會保持著一個伺服器連接。(關於如何擴容 伺服器,以處理數以千計的客戶端的連接的問題,請參閱 C10K problem。)
pip install tornado
源碼安裝
https:
/
/
pypi.python.org
/
packages
/
source
/
t
/
tornado
/
tornado
-
4.3
.tar.gz
一、快速上手
第一步:執行腳本,監聽xxxx埠
第二步:瀏覽器客戶端訪問 /index --> http://127.0.0.1:xxxx/index
第三步:伺服器接受請求,並交由對應的類處理該請求
第四步:類接受到請求之後,根據請求方式(post / get / delete ...)的不同調用並執行相應的方法
第五步:方法返回值的字元串內容發送瀏覽器
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class MainHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.write("Hello, world") 11 12 application = tornado.web.Application([ 13 ("/index", MainHandler), 14 ]) 15 16 17 if __name__ == "__main__": 18 application.listen(xxxx埠號) 19 tornado.ioloop.IOLoop.instance().start()View Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 from tornado import httpclient 7 from tornado.web import asynchronous 8 from tornado import gen 9 10 import uimodules as md 11 import uimethods as mt 12 13 class MainHandler(tornado.web.RequestHandler): 14 @asynchronous 15 @gen.coroutine 16 def get(self): 17 print 'start get ' 18 http = httpclient.AsyncHTTPClient() 19 http.fetch("http://127.0.0.1:8008/post/", self.callback) 20 self.write('end') 21 22 def callback(self, response): 23 print response.body 24 25 settings = { 26 'template_path': 'template', 27 'static_path': 'static', 28 'static_url_prefix': '/static/', 29 'ui_methods': mt, 30 'ui_modules': md, 31 } 32 33 application = tornado.web.Application([ 34 (r"/index", MainHandler), 35 ], **settings) 36 37 38 if __name__ == "__main__": 39 application.listen(8009) 40 tornado.ioloop.IOLoop.instance().start()非同步非阻塞例子
二、路由系統
路由系統其實就是 url 和 類 的對應關係,這裡不同於其他框架,其他很多框架均是 url 對應 函數,Tornado中每個url對應的是一個類。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 2 # -*- coding:utf-8 -*- 3 3 4 4 import tornado.ioloop 5 5 import tornado.web 6 6 7 7 8 8 class MainHandler(tornado.web.RequestHandler): 9 9 def get(self): 10 10 self.write("Hello, world") 11 11 12 12 class StoryHandler(tornado.web.RequestHandler): 13 13 def get(self, story_id): 14 14 self.write("You requested the story " + story_id) 15 15 16 16 class BuyHandler(tornado.web.RequestHandler): 17 17 def get(self): 18 18 self.write("buy.xxx.com/index") 19 19 20 20 application = tornado.web.Application([ 21 21 ("/index", MainHandler), 22 22 ("/story/([0-9]+)", StoryHandler), 23 23 ]) 24 24 25 25 application.add_handlers('buy.xxx.com$', [ 26 26 ('/index',BuyHandler), 27 27 ]) 28 28 29 29 if __name__ == "__main__": 30 30 application.listen(80) 31 31 tornado.ioloop.IOLoop.instance().start()View Code
三、模板
Tornao中的模板語言和django中類似,模板引擎將模板文件載入記憶體,然後將數據嵌入其中,最終獲取到一個完整的字元串,再將字元串返回給請求者。
Tornado 的模板支持“控制語句”和“表達語句”,控制語句是使用 {%
和 %}
包起來的 例如 {% if len(items) > 2 %}
。表達語句是使用 {{
和 }}
包起來的,例如 {{ items[0] }}
。
控制語句和對應的 Python 語句的格式基本完全相同。我們支持 if
、for
、while
和 try
,這些語句邏輯結束的位置需要用 {% end %}
做標記。還通過 extends
和 block
語句實現了模板繼承。這些在 template
模塊 的代碼文檔中有著詳細的描述。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 5 <link href="{{static_url("css/common.css")}}" rel="stylesheet" /> 6 {% block CSS %}{% end %} 7 </head> 8 <body> 9 10 <div class="pg-header"> 11 12 </div> 13 14 {% block RenderBody %}{% end %} 15 16 <script src="{{static_url("js/jquery-3.4.1.js")}}"></script> 17 18 {% block JavaScript %}{% end %} 19 </body> 20 </html>layout.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {% extends 'layout.html'%} 2 {% block CSS %} 3 <link href="{{static_url("css/index.css")}}" rel="stylesheet" /> 4 {% end %} 5 6 {% block RenderBody %} 7 <h1>Index</h1> 8 9 <ul> 10 {% for item in li %} 11 <li>{{item}}</li> 12 {% end %} 13 </ul> 14 15 {% end %} 16 17 {% block JavaScript %} 18 19 {% end %}index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class MainHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('home/index.html') 11 12 settings = { 13 'template_path': 'template', 14 } 15 16 application = tornado.web.Application([ 17 (r"/index", MainHandler), 18 ], **settings) 19 20 21 if __name__ == "__main__": 22 application.listen(80) 23 tornado.ioloop.IOLoop.instance().start()xxx.py
在模板中預設提供了一些函數、欄位、類以供模板使用:
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
: forhandler.static_url
的別名xsrf_form_html
:handler.xsrf_form_html
的別名
Tornado預設提供的這些功能其實本質上就是 UIMethod 和 UIModule,我們也可以自定義從而實現類似於Django的simple_tag的功能
1、定義
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # uimethods.py 2 3 def tab(self): 4 return 'UIMethod'uimethods.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 from tornado.web import UIModule 4 from tornado import escape 5 6 class custom(UIModule): 7 8 def render(self, *args, **kwargs): 9 return escape.xhtml_escape('<h1>xxx</h1>') 10 #return escape.xhtml_escape('<h1>xxx</h1>')uimodules.py
2、註冊
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 from tornado.escape import linkify 7 import uimodules as md 8 import uimethods as mt 9 10 class MainHandler(tornado.web.RequestHandler): 11 def get(self): 12 self.render('index.html') 13 14 settings = { 15 'template_path': 'template', 16 'static_path': 'static', 17 'static_url_prefix': '/static/', 18 'ui_methods': mt, 19 'ui_modules': md, 20 } 21 22 application = tornado.web.Application([ 23 (r"/index", MainHandler), 24 ], **settings) 25 26 27 if __name__ == "__main__": 28 application.listen(8009) 29 tornado.ioloop.IOLoop.instance().start()main.py
3、使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <link href="{{static_url("commons.css")}}" rel="stylesheet" /> 7 </head> 8 <body> 9 <h1>hello</h1> 10 {% module custom(123) %} 11 {{ tab() }} 12 </body>index.html
四、實用功能
1、靜態文件
對於靜態文件,可以配置靜態文件的目錄和前段使用時的首碼,並且Tornaodo還支持靜態文件緩存。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import tornado.ioloop 5 import tornado.web 6 7 8 class MainHandler(tornado.web.RequestHandler): 9 def get(self): 10 self.render('home/index.html') 11 12 settings = { 13 'template_path': 'template', 14 'static_path': 'static', 15 'static_url_prefix': '/static/', 16 } 17 18 application = tornado.web.Application([ 19 (r"/index", MainHandler), 20 ], **settings) 21 22 23 if __name__ == "__main__": 24 application.listen(80) 25 tornado.ioloop.IOLoop.instance().start()main.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <link href="{{static_url("commons.css")}}" rel="stylesheet" /> 7 </head> 8 <body> 9 <h1>hello</h1> 10 </body> 11 </html>index.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def get_content_version(cls, abspath): 2 """Returns a version string for the resource at the given path. 3 4 This class method may be overridden by subclasses. The 5 default implementation is a hash of the file's contents. 6 7 .. versionadded:: 3.1 8 """ 9 data = cls.get_content(abspath) 10 hasher = hashlib.md5() 11 if isinstance(data, bytes): 12 hasher.update(data) 13 else: 14 for chunk in data: 15 hasher.update(chunk) 16 return hasher.hexdigest()靜態文件緩存的實現
2、csrf
Tornado和Django中的相似,跨站偽造請求(Cross-site request forgery)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 settings = { 2 "xsrf_cookies": True, 3 } 4 application = tornado.web.Application([ 5 (r"/", MainHandler), 6 (r"/login", LoginHandler), 7 ], **settings)配置
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <form action="/new_message" method="post"> 2 {{ xsrf_form_html() }} 3 <input type="text" name="message"/> 4 <input type="submit" value="Post"/> 5 </form>普通表單
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 function getCookie(name) { 2 var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); 3 return r ? r[1] : undefined; 4 } 5 6 jQuery.postJSON = function(url, args, callback) { 7 args._xsrf = getCookie("_xsrf"); 8 $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST", 9 success: function(response) { 10 callback(eval("(" + response + ")")); 11 }}); 12 };Ajax 使用時,本質上就是去獲取本地的cookie,攜帶cookie再來發送請求
3、cookie
Tornado中可以對cookie進行操作,並且還可以對cookie進行簽名以放置偽造。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class MainHandler(tornado.web.RequestHandler): 2 def get(self): 3 if not self.get_cookie("mycookie"): 4 self.set_cookie("mycookie", "myvalue") 5 self.write("Your cookie was not set yet!") 6 else: 7 self.write("Your cookie was set!")基本使用
簽名
Cookie 很容易被惡意的客戶端偽造。加入你想在 cookie 中保存當前登陸用戶的 id 之類的信息,你需要對 cookie 作簽名以防止偽造。Tornado 通過 set_secure_cookie 和 get_secure_cookie 方法直接支持了這種功能。 要使用這些方法,你需要在創建應用時提供一個密鑰,名字為 cookie_secret。 你可以把它作為一個關鍵詞參數傳入應用的設置中
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class MainHandler(tornado.web.RequestHandler): 2 def get(self): 3 if not self.get_secure_cookie("mycookie"): 4 self.set_secure_cookie("mycookie", "myvalue") 5 self.write("Your cookie was not set yet!") 6 else: 7 self.write("Your cookie was set!") 8 9 application = tornado.web.Application([ 10 (r"/", MainHandler), 11 ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")View Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def _create_signature_v1(secret, *parts): 2 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 3 for part in parts: 4 hash.update(utf8(part)) 5 return utf8(hash.hexdigest()) 6 7 8 def _create_signature_v2(secret, s): 9 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 10 hash.update(utf8(s)) 11 return utf8(hash.hexdigest())內部演算法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def create_signed_value(secret, name, value, version=None, clock=None, 2 key_version=None): 3 if version is None: 4 version = DEFAULT_SIGNED_VALUE_VERSION 5 if clock is None: 6 clock = time.time 7 8 timestamp = utf8(str(int(clock()))) 9 value = base64.b64encode(utf8(value)) 10 if version == 1: 11 signature = _create_signature_v1(secret, name, value, timestamp) 12 value = b"|".join([value, timestamp, signature]) 13 return value 14 elif version == 2: 15 # The v2 format consists of a