白話 Tornado 原始碼(3):請求來了

發表於2015-09-17

上一篇《白話 Tornado 原始碼(2):待請求階段》中介紹了tornado框架在客戶端請求之前所做的準備(下圖1、2部分),本質上就是建立了一個socket服務端,並進行了IP和埠的繫結,但是未執行 socket的accept方法,也就是未獲取客戶端請求資訊。

概述

(配圖超大,請點選這裡看大圖

本篇就來詳細介紹tornado伺服器(socket服務端)是如何接收使用者請求資料以及如果根據使用者請求的URL處理並返回資料,也就是上圖的3系列所有步驟,如上圖【start】是一個死迴圈,其中利用epoll監聽服務端socket控制程式碼,一旦客戶端傳送請求,則立即呼叫HttpServer物件的_handle_events方法來進行請求的處理。

對於整個3系列按照功能可以劃分為四大部分:

  • 獲取使用者請求資料(上圖3.4)
  • 根據使用者請求URL進行路由匹配,從而使得某個方法處理具體的請求(上圖3.5~3.19)
  • 將處理後的資料返回給客戶端(上圖3.21~3.23)
  • 關閉客戶端socket(上圖3.24~3.26)

3.1、HTTPServer物件的_handle_events方法

此處程式碼主要有三項任務:

1、 socket.accept() 接收了客戶端請求。
2、建立封裝了客戶端socket物件和IOLoop物件的IOStream例項(用於之後獲取或輸出資料)。
3、建立HTTPConnection物件,其內容是實現整個功能的邏輯。

3.2、IOStream的__init__方法

此處程式碼主要兩專案任務:

  • 封裝客戶端socket和其他資訊,以便之後執行該物件的其他方法獲取客戶端請求的資料和響應客戶資訊
  • 將客戶端socket物件新增到epoll,並且指定當客戶端socket物件變化時,就去執行 IOStream的_handle_events方法(呼叫socket.send給使用者響應資料)

3.3、HTTPConnections的__init__方法

此處程式碼主要兩項任務:

  • 獲取請求資料
  • 呼叫 _on_headers 繼續處理請求

對於獲取請求資料,其實就是執行IOStream的read_until函式來完成,其內部通過socket.recv(4096)方法獲取客戶端請求的資料,並以 【\r\n\r\n】作為請求資訊結束符(http請求頭和內容通過\r\n\r\n分割)。

請求資料格式:

GET / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
If-None-Match: “e02aa1b106d5c7c6a98def2b13005d5b84fd8dc8”

詳細程式碼解析:



3.4、HTTPConnnection的 _on_headers 方法(含3.5)

上述程式碼主要有兩個任務:

  • 根據獲取的請求資訊生成響應的請求頭鍵值對,並把資訊封裝到HttpRequest物件中
  • 呼叫Application的__call__方法,繼續處理請求

3.6、Application的__call__方法(含3.7、3.8、3.9)

此處程式碼主要有三個項任務:

  • 根據請求的url和封裝在Application物件中的url對映做匹配,獲取url所對應的Handler物件。ps:Handlers泛指繼承RequestHandler的類
  • 建立Handler物件,即:執行Handler的__init__方法
  • 執行Handler物件的 _execute 方法

注意:

1、執行Application的 __call__ 方法時,其引數request是HTTPRequest物件(其中封裝HTTPConnetion、Stream、Application物件、請求頭資訊)

2、Handler泛指就是我們定義的用於處理請求的類並且她還繼承自RequestHandler


 

上述過程中,首先根據請求的URL去路由規則中匹配,一旦匹配成功,則建立路由相對應的handler的例項。例如:如果請求 的url是【/index/11】則會建立IndexHandler例項,然後再執行該物件的 _execute 方法。由於所有的 xxxHandler 類是RequestHandler的派生類,所以會預設執行 RequestHandler的 _execute 方法。

3.10 RequestHandler的_execute方法 (含有3.11、3.12、3.13)

此處程式碼主要有三項任務:

  • 擴充套件點,因為self.prepare預設是空方法,所有可以在這裡被重寫
  • 通過反射執行Handler的get/post/put/delete等方法
  • 完成請求處理後,執行finish方法

例:使用者傳送get請求


上述在執行RequestHandler的write方法時,講資料儲存在Handler物件的 _write_buffer 列表中,在之後執行finish時再講資料寫到IOStream物件的_write_buffer欄位中,其型別是雙向佇列collections.deque()。

3.14、執行RequestHandler的finish

此段程式碼主要有兩項任務:

  • 將使用者處理請求後返回的資料傳送到IOStream的_write_buffer佇列中
  • 紀錄操作日誌

3.15、執行RequestHandler的flush方法

此處程式碼主要有一項任務:

  • 將處理請求返回的資料傳送到IOStream的_write_buffer佇列中


以上程式碼執行完成之後,請求的處理基本上就完成了。下面就是等待監聽客戶端socket控制程式碼的epoll觸發,然後執行IOStream的_handle_event方法來將 響應資料傳送給客戶端。

3.20、執行RequestHandler的_log方法

此處程式碼主要有一項任務:

  • 記錄操作日誌(利用logging模組)

3.21、IOStream的Handle_event方法

由於epoll中不但監聽了伺服器socket控制程式碼還監聽了客戶端sokcet控制程式碼,所以當客戶端socket物件變化時,就會去呼叫之前指定的IOStream的_handler_events方法。

此段程式碼主要有一項任務:

  • 將處理之後的響應資料傳送給客戶端

3.22、IOStream的_handle_write方法

此段程式碼主要有兩項任務:

  • 呼叫socket.send給客戶端傳送響應資料
  • 執行回撥函式HTTPConnection的_on_write_complete方法

注:IOStream的_run_callback方法內部呼叫了HTTPConnection的_on_write_complete方法

3.23、執行HTTPConnection的_on_write_complete方法

此處程式碼主要有一項任務:

  • 更新客戶端socket所在epoll中的狀態為【READ】,以便之後執行3.24時關閉socket客戶端。



3.24、IOStream的_handle_write方法(含3.25、3.26)

此段程式碼主要有一項任務:

  • 關閉客戶端socket


 


結束語

以上就是tornado原始碼針對請求的主要內容,另外,大家可能注意到我們返回給使用者的只是一個簡單的“hello world”,tornado返回複雜的內容時又需要使用模板語言,至於如何生成複雜的頁面,我們會在下一篇再會剖析。

讀者如果覺得那裡錯誤或不適,請與我聯絡!!!

相關文章