Tornado 4.3文檔翻譯: 用戶指南-web應用的結構

譯者說

Tornado 4.3於2015年11月6日發佈,該版本正式支持Python3.5async/await關鍵字,而且用舊版本CPython編譯Tornado一樣可使用這兩個關鍵字,這無疑是一種進步。其次,這是最後一個支持Python2.6Python3.2的版本了,在後續的版本了會移除對它們的兼容。如今網絡上尚未Tornado4.3的中文文檔,因此爲了讓更多的朋友能接觸並學習到它,我開始了這個翻譯項目,但願感興趣的小夥伴能夠一塊兒參與翻譯,項目地址是tornado-zh on Github,翻譯好的文檔在Read the Docs上直接能夠看到。歡迎Issues or PR。html

Tornado web應用的結構

一般一個Tornado web應用包括一個或者多個RequestHandler 子類,一個能夠將收到的請求路由到對應handler的Application 對象,和一個啓動服務的 main() 函數.python

一個最小的"hello world"例子就像下面這樣:git

import tornado.ioloop
    import tornado.web

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")

    def make_app():
        return tornado.web.Application([
            (r"/", MainHandler),
        ])

    if __name__ == "__main__":
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()

Application 對象

Application對象是負責全局配置的,包括映射請求轉發給處理程序的路由表.github

路由表是URLSpec對象(或元組)的列表, 其中每一個都包含(至少)一個正則表達式和一個處理類. 順序問題; 第一個匹配的規則會被使用. 若是正則表達式包含捕獲組, 這些組會被做爲 路徑參數 傳遞給處理函數的HTTP方法.若是一個字典做爲 URLSpec 的第三個參數被傳遞, 它會做爲 初始參數傳遞給 RequestHandler.initialize. 最後 URLSpec 可能有一個名字(name), 這將容許它被 RequestHandler.reverse_url 使用.web

例如, 在這個片斷中根URL / 映射到了MainHandler , 像 /story/ 後跟着一個數字這種形式的URL被映射到了StoryHandler. 這個數字被傳遞(做爲字符串)給StoryHandler.get.正則表達式

class MainHandler(RequestHandler):
        def get(self):
            self.write('<a href="%s">link to story 1</a>' %
                       self.reverse_url("story", "1"))

    class StoryHandler(RequestHandler):
        def initialize(self, db):
            self.db = db

        def get(self, story_id):
            self.write("this is story %s" % story_id)

    app = Application([
        url(r"/", MainHandler),
        url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
        ])

Application 構造函數有不少關鍵字參數能夠用於自定義應用程序的行爲和使用某些特性(或者功能); 完整列表請查看Application.settings .json

RequestHandler 子類

Tornado web 應用程序的大部分工做是在RequestHandler子類下完成的.處理子類的主入口點是一個命名爲處理HTTP方法的函數: get(),post(), 等等. 每一個處理程序能夠定義一個或者多個這種方法來處理不一樣的HTTP動做. 如上所述, 這些方法將被匹配路由規則的捕獲組對應的參數調用.api

在處理程序中, 調用方法如RequestHandler.render 或者RequestHandler.write 產生一個響應. render() 經過名字加載一個Template 並使用給定的參數渲染它. write() 被用於非模板基礎的輸出; 它接受字符串, 字節, 和字典(字典會被編碼成JSON).瀏覽器

RequestHandler 中的不少方法的設計是爲了在子類中複寫和在整個應用中使用. 經常使用的方法是定義一個 BaseHandler 類, 複寫一些方法例如RequestHandler.write_errorRequestHandler.get_current_user而後子類繼承使用你本身的 BaseHandler 而不是RequestHandler在你全部具體的處理程序中.緩存

處理輸入請求

處理請求的程序(request handler)可使用 self.request 訪問表明當前請求的對象. 經過tornado.httputil.HTTPServerRequest 的類定義查看完整的屬性列表.

使用HTML表單格式請求的數據會被解析而且能夠在一些方法中使用, 例如RequestHandler.get_query_argumentRequestHandler.get_body_argument.

class MyFormHandler(tornado.web.RequestHandler):
        def get(self):
            self.write('<html><body><form action="/myform" method="POST">'
                       '<input type="text" name="message">'
                       '<input type="submit" value="Submit">'
                       '</form></body></html>')

        def post(self):
            self.set_header("Content-Type", "text/plain")
            self.write("You wrote " + self.get_body_argument("message"))

因爲HTLM表單編碼不肯定一個標籤的參數是單一值仍是一個列表,RequestHandler 有明確的方法來容許應用程序代表是否它指望接收一個列表.對於列表, 使用RequestHandler.get_query_argumentsRequestHandler.get_body_arguments 而不是它們的單數形式.

經過一個表單上傳的文件可使用 self.request.files,它遍歷名字(HTML 標籤 <input type="file"> 的name)到一個文件列表.每一個文件都是一個字典的形式{"filename":..., "content_type":..., "body":...}. files對象是當前惟一的若是文件上傳是經過一個表單包裝(i.e. a multipart/form-data Content-Type); 若是沒用這種格式,原生上傳的數據能夠調用 self.request.body 使用.默認上傳的文件是徹底緩存在內存中的; 若是你須要處理佔用內存太大的文件能夠看看 stream_request_body 類裝飾器.

因爲HTML表單編碼格式的怪異 (e.g. 在單數和複數參數的含糊不清), Tornado不會試圖統一表單參數和其餘輸入類型的參數. 特別是, 咱們不解析JSON請求體.應用程序但願使用JSON代替表單編碼能夠複寫 RequestHandler.prepare來解析它們的請求:

def prepare(self):
        if self.request.headers["Content-Type"].startswith("application/json"):
            self.json_args = json.loads(self.request.body)
        else:
            self.json_args = None

複寫RequestHandler的方法

除了 get()/post()/等, 在 .RequestHandler 中的某些其餘方法被設計成了在必要的時候讓子類重寫. 在每一個請求中, 會發生下面的調用序列:

  1. 在每次請求時生成一個新的 RequestHandler 對象

  2. RequestHandler.initialize()Application 配置中的初始化參數被調用. initialize 一般應該只保存成員變量傳遞的參數; 它不可能產生任何輸出或者調用方法, 例如RequestHandler.send_error.

  3. RequestHandler.prepare() 被調用. 這在你全部處理子類共享的基類中是最有用的, 不管是使用哪一種HTTP方法, prepare 都會被調用.prepare 可能會產生輸出; 若是它調用 RequestHandler.finish(或者 redirect, 等), 處理會在這裏結束.

  4. 其中一種HTTP方法被調用: get(), post(), put(),等. 若是URL的正則表達式包含捕獲組, 它們會被做爲參數傳遞給這個方法.

  5. 當請求結束, RequestHandler.on_finish() 方法被調用. 對於同步處理程序會在 get() (等)後當即返回; 對於異步處理程序,會在調用RequestHandler.finish() 後返回.

全部這樣設計被用來複寫的方法被記錄在了RequestHandler的文檔中.其中最經常使用的一些被複寫的方法包括:

  • RequestHandler.write_error - 輸出對錯誤頁面使用的HTML.

  • RequestHandler.on_connection_close - 當客戶端斷開時被調用;應用程序能夠檢測這種狀況,並中斷後續處理. 注意這不能保證一個關閉的鏈接及時被發現.

  • RequestHandler.get_current_user - 參考 user-authentication

  • RequestHandler.get_user_locale - 返回 .Locale 對象給當前
    用戶使用

  • RequestHandler.set_default_headers - 能夠被用來設置額外的響應
    頭(例如自定義的 Server 頭)

錯誤處理

若是一個處理程序拋出一個異常, Tornado會調用RequestHandler.write_error 來生成一個錯誤頁.tornado.web.HTTPError 能夠被用來生成一個指定的狀態碼; 全部其餘的異常都會返回一個500狀態.

默認的錯誤頁面包含一個debug模式下的調用棧和另一行錯誤描述(e.g. "500: Internal Server Error"). 爲了建立自定義的錯誤頁面, 複寫RequestHandler.write_error (可能在一個全部處理程序共享的一個基類裏面).這個方法可能產生輸出一般經過一些方法, 例如 RequestHandler.writeRequestHandler.render. 若是錯誤是由異常引發的, 一個 exc_info 將做爲一個關鍵字參數傳遞(注意這個異常不能保證是 sys.exc_info 當前的異常, 因此 write_error 必須使用 e.g. traceback.format_exception 代替traceback.format_exc).

也能夠在常規的處理方法中調用 RequestHandler.set_status 代替write_error 返回一個(自定義)響應來生成一個錯誤頁面. 特殊的例外tornado.web.Finish 在直接返回不方便的狀況下可以在不調用 write_error前結束處理程序.

對於404錯誤, 使用 default_handler_class Application setting. 這個處理程序會複寫RequestHandler.prepare 而不是一個更具體的方法, 例如 get()因此它能夠在任何HTTP方法下工做. 它應該會產生如上所說的錯誤頁面: 要麼raise一個 HTTPError(404) 要麼複寫 write_error, 或者調用self.set_status(404) 或者在 prepare() 中直接生成響應.

重定向

這裏有兩種主要的方式讓你能夠在Tornado中重定向請求:RequestHandler.redirect 和使用 RedirectHandler.

你能夠在一個 RequestHandler 的方法中使用 self.redirect() 把用戶重定向到其餘地方. 還有一個可選參數 permanent 你可使用它來代表這個重定向被認爲是永久的. permanent 的默認值是 False, 這會生成一個302 Found HTTP響應狀態碼, 適合相似在用戶的 POST 請求成功後的重定向.若是 permanent 是true, 會使用 301 Moved Permanently HTTP響應, 更適合e.g. 在SEO友好的方法中把一個頁面重定向到一個權威的URL.

RedirectHandler 讓你直接在你 Application 路由表中配置. 例如, 配置一個靜態重定向:

app = tornado.web.Application([
        url(r"/app", tornado.web.RedirectHandler,
            dict(url="http://itunes.apple.com/my-app-id")),
        ])

RedirectHandler 也支持正則表達式替換. 下面的規則重定向全部以 /pictures/開始的請求用 /photos/ 前綴代替:

app = tornado.web.Application([
        url(r"/photos/(.*)", MyPhotoHandler),
        url(r"/pictures/(.*)", tornado.web.RedirectHandler,
            dict(url=r"/photos/\1")),
        ])

不像 RequestHandler.redirect, RedirectHandler 默認使用永久重定向.這是由於路由表在運行時不會改變, 並且被認爲是永久的.當在處理程序中發現重定向的時候, 多是其餘可能改變的邏輯的結果.用 .RedirectHandler 發送臨時重定向, 須要添加 permanent=False.RedirectHandler 的初始化參數.

異步處理

Tornado默認會同步處理: 當 get()/post() 方法返回, 請求被認爲結束而且返回響應. 由於當一個處理程序正在運行的時候其餘全部請求都被阻塞,任何須要長時間運行的處理都應該是異步的, 這樣它就能夠在非阻塞的方式中調用它的慢操做了. 這個話題更詳細的內容包含在async 中; 這部分是關於在 RequestHandler 子類中的異步技術的細節.

使用 coroutine 裝飾器是作異步最簡單的方式. 這容許你使用 yield 關鍵字執行非阻塞I/O, 而且直到協程返回才發送響應. 查看 coroutines瞭解更多細節.

在某些狀況下, 協程不如回調爲主的風格方便, 在這種狀況下tornado.web.asynchronous 裝飾器能夠用來代替. 當使用這個裝飾器的時候,響應不會自動發送; 而請求將一直保持開放直到callback調用RequestHandler.finish. 這須要應用程序確保這個方法被調用或者其餘用戶的瀏覽器簡單的掛起.

這裏是一個使用Tornado's 內置的 AsyncHTTPClient 調用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().

爲了方便對比, 這裏有一個使用協程的相同的例子:

class MainHandler(tornado.web.RequestHandler):
        @tornado.gen.coroutine
        def get(self):
            http = tornado.httpclient.AsyncHTTPClient()
            response = yield http.fetch("http://friendfeed-api.com/v2/feed/bret")
            json = tornado.escape.json_decode(response.body)
            self.write("Fetched " + str(len(json["entries"])) + " entries "
                       "from the FriendFeed API")

更多高級異步的示例, 請看chat example application, 實現了一個使用 長輪詢(long polling)的AJAX聊天室.使用長輪詢的用戶可能想要覆蓋 on_connection_close() 來在客戶端關閉鏈接以後進行清理(注意看方法的文檔來查看警告).

相關文章
相關標籤/搜索