1. 程式人生 > >Tornado Web 框架

Tornado Web 框架

str bre 服務器端 xtend erro 錯誤 需要 django title

一、簡介

  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 的方法函數

對於一個請求的處理過程代碼調用次序如下:

  1. 程序為每一個請求創建一個 RequestHandler 對象;
  2. 程序調用 initialize() 函數,這個函數的參數是 Application 配置中的關鍵字參數定義。(initialize 方法是 Tornado 1.1 中新添加的,舊版本中你需要重寫 __init__ 以達到同樣的目的) initialize 方法一般只是把傳入的參數存到成員變量中,而不會產生一些輸出或者調用像 send_error 之類的方法。
  3. 程序調用 prepare()。無論使用了哪種 HTTP 方法,prepare 都會被調用到,因此這個方法通常會被定義在一個基類中,然後在子類中重用。prepare可以產生輸出信息。如果它調用了finish(或send_error` 等函數),那麽整個處理流程就此結束。
  4. 程序調用某個 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 語句的格式基本完全相同。我們支持 ifforwhiletry,這些語句邏輯結束的位置需要用 {% end %} 做標記。還通過 extendsblock 語句實現了模板繼承。這些在 template 模塊 的代碼文檔中有著詳細的描述。

註:在使用模板前需要在setting中設置模板路徑:"template_path" : "views"

1、基本使用

技術分享 app.py 技術分享 index.html 技術分享 其他方法

2、母版(模板繼承)

技術分享 layout.html 技術分享 extends.html

3、導入

技術分享 header.html 技術分享 index.html

4、自定義UIMethod以UIModule

a.定義

技術分享 uimethods.py 技術分享 uimodules.py

b.註冊

技術分享 app.py

c.使用

技術分享 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_cookieget_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實現用戶驗證-Demo

3、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_userNone

要在應用程序實現用戶認證的功能,你需要重寫請求處理中 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 + ")"));
    }});
};
技術分享 技術分享

對於 PUTDELETE 請求(以及不使用將 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 技術分享 Python

2、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 機制,我們可以自定義如下:

技術分享 demo

B. 表單驗證

在Web程序中往往包含大量的表單驗證的工作,如:判斷輸入是否為空,是否符合規則。

技術分享 HTML 技術分享 Python

由於驗證規則可以代碼重用,所以可以如此定義:

技術分享 demo

Tornado Web 框架