4.Tornado對Web請求與響應的處理機制 (副標題:作爲Web Server的功能)

接下來我們看一下helloword.py的唯一一個handler。

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

它是tornado.web.RequestHandler的一個子類,覆蓋了父類的get方法。get方法也極簡單,直接寫一個“hello world”字符串到客戶端。

不難想到,Tornado在接到用戶請求http://127.0.0.1:8888/時,最終會調用我們MainHandler的get方法。這中間經過了很多流程和邏輯,我們會一一跟蹤並證實。

接下來再看main函數的下一句。

1 http_server = tornado.httpserver.HTTPServer(application)

看起來我們是新建了一個http server的實例,前面創建好的application作爲參數傳遞構了httpserver的構造函數。HTTPServer類定義在tornado/httpserver.py中。顯然這是男主角。它的註釋說明比Application還要長,需要重點關注。

tornadoe中的httpserver的概念,簡單概括下來就是:讀取客戶端的http request,調用對應的handler,然後用HTTPServer.write函數把數據返回給客戶端。

在註釋中,作者舉了一個最簡單的例子來說明這個概念(甚至不需要用到Handler類的參與):

01 import tornado.httpserver
02 import tornado.ioloop
03  
04 def handle_request(request):
05     message = "You requested %s\n" % request.uri
06     request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
07                    len(message), message))
08     request.finish()
09  
10     http_server = tornado.httpserver.HTTPServer(handle_request)
11     http_server.listen(8888)
12     tornado.ioloop.IOLoop.instance().start()

看到有多簡單沒有,一個handle_request函數就可以支撐起一個網站。當然,這個網站功能很簡陋,不過是把客戶的請求寫回去而已。

你當然可以在handler_request函數裏大作文章,針對不同的url執行不同的代碼,同樣能達到前面Handler機制的效果,不過,tornado已經將這樣的需求提煉出一套非常高效的handler機制,用起來也非常舒服。如果沒有特別原因,就不必去“重複造輪子”的工作。

這也是爲什麼我們一般稱之爲web framework,tornado已經把一個http server各流程的基礎框架搭好了,你只需要填填空,客製化,“裝修”一下。

tornado 的HTTPServer會負責解析用戶的HTTP Request,構造一個request對象。交給後面的RequestHandler處理。Request的解析是一個規範化的流程,基本不需要客製化。針對Request的處理函數(即RequestHandler)纔是重點被客戶化的部分。

關於HTTPServer的分析將會佔很大的篇幅,我們留在後面專門研究。在helloworld的分析中,我們只要記住,Application將會和HTTPServer實例綁定。

1 http_server.listen(options.port)

HTTP是工作在TCP協議上的,所以它其實是TCPServer的派生類。有過socket編程經驗的讀者都會記得,啓動一個TCP服務器有三個必備步驟:

  1. create socket 創建socket。
  2. bind address 綁定地址。
  3. listen 執行偵聽。

TCPServer類的實現順理成章的借鑑了Unix/Linux中socket的機制。所以它也存在上面的幾個步驟,並且這幾個步驟都是在HTTPServer.listen()函數調用時完成的。現在不會提這些細節,留在後面我們分析TCPServer這個類時再詳查。

listen函數的參數是端口號,前面提到,tornado demo的默認端口號都是8888,從helloworld.py的前面幾行就可以看到。

1 from tornado.options import define, options
2 define("port", default=8888help="run on the given port"type=int)

define函數是OptionParser類的成員,定義在tornado/options.py中,機制與parse_command_line()類似。上面就是定義了一個int變量,名爲port,默認值爲8888,還附帶一個說明文字,厲害。port變量會被存放進options對象的一個dictionary成員中。可以看到,訪問方式就是options.port。執行完http_server.listen(options.port),你的PC上就會在options.port這個端口上啓動一個服務器,開始偵聽用戶的連接。

看完前面的http_server.listen(),似乎感覺少點什麼。對了,沒有掉到關鍵的accept函數,用戶連接又怎麼處理呢?別急,馬上就是。這一句看上去很玄乎的東西,其實就相當於我們熟悉的accept。

1 tornado.ioloop.IOLoop.instance().start()

那爲什麼不直接來個accept?這個IOLoop又是什麼東西?

IOLoop與TCPServer之間的關係其實很簡單。回憶用C語言寫TCP服務器的情景,我們寫好了create-bind-listen三段式後,其實事情還不算完。我們還得寫點代碼處理accept/recv/send呢。通常我們會寫一個無限循環,不斷調用accept來響應客戶端鏈接。這個無限循環就是這裏的IOLoop。這些代碼讓大家去寫,最後其實都大同小異,因此tornado乾脆寫了一套標準代碼,封裝在IOLoop裏。

IOLoop會負責accept這一步。你可以註釋掉IOLoop這一行,然後再訪問http://127.0.0.1:8888/,就會發現根本連不上服務器。通過抓包會發現,實際上服務器並沒有響應連接請求。

對於recv/send操作,通常也是在一個循環中進行的,也可以抽象成IOLoop。我們分析HTTPServer類的實現時,會看到它是怎麼藉助IOLoop工作的。

到此爲止我們的web server已經完備了。

我們在瀏覽器裏輸入http://127.0.0.1:8888/,瀏覽器就會連接到我們的服務器,把HTTP請求送到HTTPServer中。 HTTPServer會先parse request,然後將reqeust交給第一個匹配到的Handler。Handler負責組織數據,調用發送API把數據傳到客戶端。

道理就是這樣。

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