tornado應用結構

Tornado web應用的結構

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

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

    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對象是負責全局配置的,包括映射請求轉發給處理程序的路由表.

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

例如, 在這個片段中根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 .

RequestHandler 子類

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

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

RequestHandler 中的很多方法的設計是爲了在子類中複寫和在整個應用中使用. 常用的方法是定義一個 BaseHandler 類, 複寫一些方法例如RequestHandler.write_error 和RequestHandler.get_current_user然後子類繼承使用你自己的 BaseHandler 而不是RequestHandler在你所有具體的處理程序中.

處理輸入請求

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

使用HTML表單格式請求的數據會被解析並且可以在一些方法中使用, 例如RequestHandler.get_query_argument 和RequestHandler.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_arguments 和RequestHandler.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.write 和RequestHandler.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.redirectRedirectHandler 默認使用永久重定向.這是因爲路由表在運行時不會改變, 而且被認爲是永久的.當在處理程序中發現重定向的時候, 可能是其他可能改變的邏輯的結果.用 .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() 來在客戶端關閉連接之後進行清理(注意看方法的文檔來查看警告).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章