本篇文章分析 HTTPHandler 類,它在 HTTPHandler.py 文件中。
上一篇我們講到, RawServer 只負責網絡 I/O ,也就是從網絡上讀取和發送數據,至于讀到的數據如何分析,以及應該發送什么樣的數據,則交給 Handler 類來處理。如果是用 c++ 來實現的話,那么 Handler 應該是一個接口類(提供幾個虛函數作為接口),但是 python 動態語言的特性,并不需要專門定義這么一個接口類,所以實際上并沒有 Handler 這么一個類。任何一個提供了以下成員函數的類,都可以作為一個 Handler 類來與 RawServer 配合,它們是:
external_connection_made() :在建立新的連接的時候被調用
data_came_in() :連接上有數據可讀的時候被調用
connection_flushed() :當在某個連接上發送完數據之后被調用
HTTPHandler 就是這樣一個 Handler 類,它具備以上接口。
HTTPHandler 代碼很少,因為它把主要工作又交給 HTTPConnection 了。
我們看 HTTPHandler 類的這幾個函數:
l external_connection_made() :
每當新來一個連接的時候,就創建一個 HTTPConnection 類。
l data_came_in() :
當連接上有數據可讀的時候,調用 HTTPConnection::data_came_in() 。我們接下去看 HTTPConnection::data_came_in() 。
我們知道, BT client 端與 tracker 服務器之間是通過 tracke HTTP 協議來進行通信的。 HTTP 協議分為請求( request )和響應( response ),具體的協議請看相關的 RFC 文檔。我這里簡單講一下。
對 tracke 服務器來說,它讀到的數據是 client 端的 HTTP 請求。
HTTP 請求以行為單位,行的結束符是“回車換行”,也就是 ascii 字符 “ \r ”和“ \n ”。
第一行是請求的 URL ,例如:
GET /announce?ip=aaaaa;port=bbbbbbb HTTP/1.0
這行數據被空格分為三部分,
第一部分 GET 表示命令,其它命令還有 POST 、 HEAD 等等,常用的就是 GET 了。
第二部分是請求的 URL ,這里是 /announce?ip=aaaaa;port=bbbbbbb 。如果是普通的上網瀏覽網頁,那么 URL 就是我們要看的網頁在該 web 服務器上的相對路徑。但是,這里的 URL 僅僅是交互信息的一種方式, client 端把要報告給 tracker 的信息,放在 URL 中,例子里面是 ip 和 port ,更詳細的信息請看“ BT 協議規范”中 tracker 協議部分。
第三部分是 HTTP 協議的版本號,在程序中忽略。
接下來的每一行,都是 HTTP 協議的消息頭部分,例如:
Host:www.sina.com.cn
Accept-encoding:gzip
通過消息頭, tracker 服務器可以知道 client 端的一些信息,這其中比較重要的就是 Accept-encoding ,如果是 gzip ,那么說明 client 可以對 gzip 格式的數據進行解壓,那么 tracker 服務器就可以考慮用 gzip 把響應數據壓縮之后再傳回去,以減少網絡流量。我們可以在代碼中看到相應的處理。
在消息頭的最后,是一個空行,表示消息頭結束了。對 GET 和 HEAD 命令來說,消息頭的結束,也就意味著整個 client 端的請求結束了。而對 POST 命令來說,可能后面還跟著其它數據。由于我們的 tracker 服務器只接受 GET 和 HEAD 命令,所以在協議處理過程中,如果遇到空行,那么就表示處理結束。
HTTPConnection::data_came_in() 用一個循環來進行協議分析:
首先是尋找行結束符號:
i = self.buf.index('\n')
(我認為僅僅找 “ \n ”并不嚴謹,應該找 “ \r\n ”這個序列)。
如果沒有找到,那么 index() 函數會拋出一個異常,而異常的處理是返回 True ,表示數據不夠,需要繼續讀數據。
如果找到了,那么 i 之前的字符串就是完整的一行。于是調用協議處理函數,代碼是:
self.next_func = self.next_func(val)
在 HTTPConnection 的初始化的時候,有這么一行代碼:
self.next_func = self.read_type
next_func 是用來保存協議處理函數的,所以,第一個被調用的協議處理函數就是 read_type() 。它用來分析 client 端請求的第一行。在 read_type() 的最后,我們看到:
return self.read_header
這樣,在下一次調用 next_func 的時候,就是調用 read_header() 了,也就是對 HTTP 協議的消息頭進行分析。
下面先看 read_type() ,
它首先把 GET 命令中的 URL 部分保存到 self.path 中,因為這是 client 端最關鍵的信息,后面要用到。
然后檢查一下是否是 GET 或者 HEAD 命令,如果不是,那么說明數據有錯誤。返回 None ,否則 return self.read_header
接下來我們看 read_header() ,
這其中,最重要的就是對空行的處理,因為前面說了,空行表示協議分析結束。
在檢查完 client 端是否支持 gzip 編碼之后,調用:
r = self.handler.getfunc(self, self.path, self.headers)
通過一層層往后追查,發現 getfunc() 實際是 Tracker::get() ,也就是說,真正對 client 端發來的請求進行分析,以及決定如何響應,是由 Tracker 來決定的。是的,這個 Tracker 在我們 tracker 服務器源碼分析系列的第一篇文章中就已經看到了。在創建 RawServer 之后,馬上就創建了一個 Tracker 對象。所以,要了解 tracker 服務器到底是如何工作的,需要我們深入進去分析 Tracker 類,那就是我們下一篇文章的工作了。
在調用完 Tracker::get() 之后,返回的是決定響應給 client 端的數據,
if r is not None:
self.answer(r)
最后,調用 answer() 來把這些數據發送給 client 端。
對 answer() 的分析,我們在下一篇分析 Tracker 類的文章中一并講解。
l connection_flushed() :
tracker 服務器用的是非阻塞的網絡 I/O ,所以不能保證在一次發送數據的操作中,把要發送的數據全部發送出去。
這個函數,檢查在某個連接上需要發送的數據,是否已經全部被發送出去了,如果是的話,那么關閉這個連接的發送端。(為什么僅僅關閉發送端,而不是完全關閉這個連接了?疑惑)。