Tornado Web 框架
一、簡介
Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 服務器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過為了能有效利用非阻塞式服務器環境,這個 Web 框架還包含了一些相關有用工具及優化。
Tornado 和現在的主流 Web 服務器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式服務器,而且速度相當快。得利於其非阻塞的方式和對 epoll 的運用,Tornado 每秒可以處理數以千計的連接,這就意味著對於實時的 Web 服務來說,Tornado 是一個理想的 Web 框架。開發這個 Web 服務器的主要目的就是為了處理 FriendFeed 的實時功能 ——在 FriendFeed 的應用裏每一個活動用戶都會保持著一個服務器連接。(關於如何擴容服務器,以處理數以千計的客戶端的連接的問題,請參閱 C10K problem。)
請參見 Tornado 文檔 或 Tornado 原文文檔(鏡像)以詳細了解該 Web 框架。
下載和安裝
pip安裝 pip3 install tornado 源碼安裝 tar xvzf tornado-4.4.1.tar.gz cd tornado-4.4.1 python setup.py build sudo python setup.py install
源碼下載:tornado-1.2.1.tar.gz、 tornado-4.4.1.tar.gz
Tornado 各主要模塊 web - FriendFeed 使用的基礎 Web 框架,包含了 Tornado 的大多數重要的功能 escape - XHTML, JSON, URL 的編碼/解碼方法 database - 對 MySQLdb 的簡單封裝,使其更容易使用 template - 基於 Python 的 web 模板系統 httpclient - 非阻塞式 HTTP 客戶端,它被設計用來和 web 及 httpserver 協同工作 auth - 第三方認證的實現(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth) locale - 針對本地化和翻譯的支持 options - 命令行和配置文件解析工具,針對服務器環境做了優化 底層模塊 httpserver - 服務於 web 模塊的一個非常簡單的 HTTP 服務器的實現 iostream - 對非阻塞式的 socket 的簡單封裝,以方便常用讀寫操作 ioloop - 核心的 I/O 循環
二、Hello, world
"Hello, world" 及 Application settings 基本配置:
import tornado.ioloop import tornado.web # import uimodules as md # import uimethods as mt class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") settings = { ‘template_path‘: ‘views‘, # html文件 ‘static_path‘: ‘statics‘, # 靜態文件(css,js,img) ‘static_url_prefix‘: ‘/statics/‘,# 靜態文件前綴 ‘cookie_secret‘: ‘suoning‘, # cookie自定義字符串加鹽 # ‘xsrf_cookies‘: True, # 防止跨站偽造 # ‘ui_methods‘: mt, # 自定義UIMethod函數 # ‘ui_modules‘: md, # 自定義UIModule類 } application = tornado.web.Application([ (r"/", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
1、處理程序和參數
請求來時,程序會用正則匹配相應路由地址,並交付於 tornado.web.RequestHandler 的子類處理;子類會根據請求方式(post / get / delete ...)的不同調用並執行相應的方法,方法返回字符串內容並發送到瀏覽器。
self.write("<h1>Hello, World</h1>") # html代碼直接寫在瀏覽器客戶端 self.render("index.html") # 返回html文件,調用render_string(),內部其實是打開並讀取文件,返回內容 self.redirect("http://www.cnblogs.com/suoning",permanent=False) # 跳轉重定向,參數代表是否永久重定向 name = self.get_argument("name") # 獲取客戶端傳入的參數值 name = self.get_arguments("name") # 獲取多個值,類別形式 file = self.request.files["filename"] # 獲取客戶端上傳的文件 raise tornado.web.HTTPError(403) # 返回錯誤信息給客戶端
2、重寫 RequestHandler 的方法函數
對於一個請求的處理過程代碼調用次序如下:
- 程序為每一個請求創建一個 RequestHandler 對象;
- 程序調用
initialize()
函數,這個函數的參數是Application
配置中的關鍵字參數定義。(initialize
方法是 Tornado 1.1 中新添加的,舊版本中你需要重寫__init__
以達到同樣的目的)initialize
方法一般只是把傳入的參數存到成員變量中,而不會產生一些輸出或者調用像send_error
之類的方法。 - 程序調用
prepare()
。無論使用了哪種 HTTP 方法,prepare
都會被調用到,因此這個方法通常會被定義在一個基類中,然後在子類中重用。prepare
可以產生輸出信息。如果它調用了finish
(或send_error` 等函數),那麽整個處理流程就此結束。 - 程序調用某個 HTTP 方法:例如
get()
、post()
、put()
等。如果 URL 的正則表達式模式中有分組匹配,那麽相關匹配會作為參數傳入方法。
重寫 initialize()
函數(會在創建RequestHandler對象後調用):
class ProfileHandler(tornado.web.RequestHandler): def initialize(self,database): self.database = database def get(self): self.write("result:" + self.database) application = tornado.web.Application([ (r"/init", ProfileHandler, dict(database="database")) ])
四、模板引擎
Tornao中的模板語言和django中類似,模板引擎將模板文件載入內存,然後將數據嵌入其中,最終獲取到一個完整的字符串,再將字符串返回給請求者。
Tornado 的模板支持“控制語句”和“表達語句”,控制語句是使用 {%
和 %}
包起來的 例如 {% if len(items) > 2 %}
。表達語句是使用 {{
和 }}
包起來的,例如 {{ items[0] }}。
控制語句和對應的 Python 語句的格式基本完全相同。我們支持 if
、for
、while
和 try
,這些語句邏輯結束的位置需要用 {% end %}
做標記。還通過 extends
和 block
語句實現了模板繼承。這些在 template
模塊 的代碼文檔中有著詳細的描述。
註:在使用模板前需要在setting中設置模板路徑:"template_path" : "views"
1、基本使用
app.py index.html 其他方法2、母版(模板繼承)
layout.html extends.html3、導入
header.html index.html4、自定義UIMethod以UIModule
a.定義
uimethods.py uimodules.pyb.註冊
app.pyc.使用
index.html五、靜態文件和主動式文件緩存
在應用配置 settings 中指定 static_path
選項來提供靜態文件服務;
在應用配置 settings 中指定 static_url_prefix
選項來提供靜態文件前綴服務;
在導入靜態文件時用 {{static_url(‘XX.css‘)}}
方式實現主動緩存靜態文件
settings = { ‘template_path‘: ‘views‘, ‘static_path‘: ‘static‘, ‘static_url_prefix‘: ‘/static/‘, }
<head lang="en"> <title>Nick</title> <link href="{{static_url("commons.css")}}" rel="stylesheet" /> </head>
六、 Cookie
1、基本Cookie
set_cookie
方法在用戶的瀏覽中設置 cookie;
get_cookie
方法在用戶的瀏覽中獲取 cookie。
class MainHandler(tornado.web.RequestHandler): def get(self): if not self.get_cookie("mycookie"): self.set_cookie("mycookie", "myvalue") 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=")內部算法源碼解讀
加密Cookice的本質:
寫cookie過程:
- 將值進行base64加密
- 對除值以外的內容進行簽名,哈希算法(無法逆向解析)
- 拼接 簽名 + 加密值
讀cookie過程:
- 讀取 簽名 + 加密值
- 對簽名進行驗證
- base64解密,獲取值內容
註:許多API驗證機制和安全cookie的實現機制相同。
基於Cookie實現用戶驗證-Demo 基於簽名Cookie實現用戶驗證-Demo3、JavaScript操作Cookie
由於Cookie保存在瀏覽器端,所以在瀏覽器端也可以使用JavaScript來操作Cookie
/* 設置cookie,指定秒數過期 */ function setCookie(name,value,expires){ var temp = []; var current_date = new Date(); current_date.setSeconds(current_date.getSeconds() + 5); document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString(); }
對於參數:
- domain 指定域名下的cookie
- path 域名下指定url中的cookie
- secure https使用
註:jQuery中也有指定的插件 jQuery Cookie 專門用於操作cookie,猛擊這裏
七、用戶認證
當前已經認證的用戶信息都被保存在每一個請求處理器的 self.current_user
當中, 同時在模板的 current_user
中也是。默認情況下,current_user
為 None
。
要在應用程序實現用戶認證的功能,你需要重寫請求處理中 get_current_user()
這 個方法,在其中判定當前用戶的狀態,比如通過 cookie。下面的例子讓用戶簡單地使用一個 nickname 登陸應用,該登陸信息將被保存到 cookie 中:
class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("user") class MainHandler(BaseHandler): def get(self): if not self.current_user: self.redirect("/login") return name = tornado.escape.xhtml_escape(self.current_user) self.write("Hello, " + name) class LoginHandler(BaseHandler): def get(self): self.write(‘<html><body><form action="/login" method="post">‘ ‘Name: <input type="text" name="name">‘ ‘<input type="submit" value="Sign in">‘ ‘</form></body></html>‘) def post(self): self.set_secure_cookie("user", self.get_argument("name")) self.redirect("/") application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
對於那些必須要求用戶登陸的操作,可以使用裝飾器 tornado.web.authenticated
。 如果一個方法套上了這個裝飾器,但是當前用戶並沒有登陸的話,頁面會被重定向到 login_url
(應用配置中的一個選項),上面的例子可以被改寫成:
class MainHandler(BaseHandler): @tornado.web.authenticated def get(self): name = tornado.escape.xhtml_escape(self.current_user) self.write("Hello, " + name) settings = { "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", "login_url": "/login", } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
如果你使用 authenticated
裝飾器來裝飾 post()
方法,那麽在用戶沒有登陸的狀態下, 服務器會返回 403 錯誤。
Tornado 內部集成了對第三方認證形式的支持,比如 Google 的 OAuth 。參閱 auth
模塊 的代碼文檔以了解更多信息。 for more details. Checkauth
模塊以了解更多的細節。在 Tornado 的源碼中有一個 Blog 的例子,你也可以從那裏看到 用戶認證的方法(以及如何在 MySQL 數據庫中保存用戶數據)。
八、CSRF 跨站偽造請求的防範
跨站偽造請求(Cross-site request forgery), 簡稱為 XSRF,是個性化 Web 應用中常見的一個安全問題。前面的鏈接也詳細講述了 XSRF 攻擊的實現方式。
當前防範 XSRF 的一種通用的方法,是對每一個用戶都記錄一個無法預知的 cookie 數據,然後要求所有提交的請求中都必須帶有這個 cookie 數據。如果此數據不匹配 ,那麽這個請求就可能是被偽造的。
Tornado 有內建的 XSRF 的防範機制,要使用此機制,你需要在應用配置中加上 xsrf_cookies
設定:
settings = { "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", "login_url": "/login", "xsrf_cookies": True, } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), ], **settings)
如果設置了 xsrf_cookies
,那麽 Tornado 的 Web 應用將對所有用戶設置一個 _xsrf
的 cookie 值,如果 POST
PUT
DELET
請求中沒有這 個 cookie 值,那麽這個請求會被直接拒絕。如果你開啟了這個機制,那麽在所有 被提交的表單中,你都需要加上一個域來提供這個值。你可以通過在模板中使用 專門的函數 xsrf_form_html()
來做到這一點:
<form action="/new_message" method="post"> {{ xsrf_form_html() }} <input type="text" name="message"/> <input type="submit" value="Post"/> </form>
如果你提交的是 AJAX 的 POST
請求,你還是需要在每一個請求中通過腳本添加上 _xsrf
這個值。下面是在 FriendFeed 中的 AJAX 的 POST
請求,使用了 jQuery 函數來為所有請求組東添加 _xsrf
值:
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 + ")")); }}); };
對於 PUT
和 DELETE
請求(以及不使用將 form 內容作為參數的 POST
請求) 來說,你也可以在 HTTP 頭中以 X-XSRFToken
這個參數傳遞 XSRF token。
如果你需要針對每一個請求處理器定制 XSRF 行為,你可以重寫 RequestHandler.check_xsrf_cookie()
。例如你需要使用一個不支持 cookie 的 API, 你可以通過將 check_xsrf_cookie()
函數設空來禁用 XSRF 保護機制。然而如果 你需要同時支持 cookie 和非 cookie 認證方式,那麽只要當前請求是通過 cookie 進行認證的,你就應該對其使用 XSRF 保護機制,這一點至關重要。
九、文件上傳
1、Form表單上傳
HTML Python2、AJAX上傳(主要使用前兩種方式)
HTML - XMLHttpRequest-XHR HTML - jQuery HTML - iframe Python 擴展:基於iframe實現Ajax上傳示例十、驗證碼
安裝圖像處理模塊:pip3 install pillow
實例截圖:
驗證碼Demo源碼下載:猛擊這裏,註意導入模塊的路徑
驗證碼實例 python文件 驗證碼實例 HTML文件十一、非阻塞式異步請求
當一個處理請求的行為被執行之後,這個請求會自動地結束。因為 Tornado 當中使用了 一種非阻塞式的 I/O 模型,所以你可以改變這種默認的處理行為——讓一個請求一直保持 連接狀態,而不是馬上返回,直到一個主處理行為返回。要實現這種處理方式,只需要使用 tornado.web.asynchronous
裝飾器就可以了。
使用了這個裝飾器之後,你必須調用 self.finish()
已完成 HTTTP 請求,否則 用戶的瀏覽器會一直處於等待服務器響應的狀態:
class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): self.write("Hello, world") self.finish()
下面是一個使用 Tornado 內置的異步請求 HTTP 客戶端去調用 FriendFeed 的 API 的例 子:
class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): http = tornado.httpclient.AsyncHTTPClient() http.fetch("http://friendfeed-api.com/v2/feed/bret", callback=self.on_response) def on_response(self, response): if response.error: raise tornado.web.HTTPError(500) json = tornado.escape.json_decode(response.body) self.write("Fetched " + str(len(json["entries"])) + " entries " "from the FriendFeed API") self.finish()
例子中,當 get()
方法返回時,請求處理還沒有完成。在 HTTP 客戶端執行它的回 調函數 on_response()
時,從瀏覽器過來的請求仍然是存在的,只有在顯式調用了 self.finish()
之後,才會把響應返回到瀏覽器。
關於更多異步請求的高級例子,可以參閱 demo 中的 chat
這個例子。它是一個使用 long polling 方式 的 AJAX 聊天室。如果你使用到了 long polling,你可能需要復寫on_connection_close()
, 這樣你可以在客戶連接關閉以後做相關的清理動作。
十二、自定義Web組件
A. Session
何為 Session?Session和cookie之間的區別是什麽?
Session是一種服務器端的機制(cookie存在於瀏覽器端),用於存儲特定的用戶會話所需的信息(session是人為按照需求建立的,而cookie是系統自帶的);session必須依賴 cookice 。
Tornado 默認沒有提供 Session 機制,我們可以自定義如下:
demoB. 表單驗證
在Web程序中往往包含大量的表單驗證的工作,如:判斷輸入是否為空,是否符合規則。
HTML Python由於驗證規則可以代碼重用,所以可以如此定義:
demo
Tornado Web 框架