Tornado6.0.3-源碼分析之HTTPServer

一、前述

在開始正篇之前,先簡單瞭解下http協議。
http協議是基於客戶/服務器模型,面向tcp連接的協議。典型的http事務處理過程如下:
(1)客戶端與服務器建立連接;
(2)客戶端向服務器提出請求;
(3)服務器接受請求,並根據請求返回對應的響應;
(4)客戶端與服務器關閉連接。

Tornado在實現http協議時,有一種代理模式的味道。所謂代理,就是在中間起一個溝通協調作用的主體;以二手車交易來說,在買家和賣主之間的中間商,就是一個代理的作用,負責不斷把信息在買家和賣主之間傳遞,起一個橋樑作用。

二、相關類之間的關係

在http server的相關實現中,涉及到多個不同的類。先簡單過一個基本的類間關係,有利於查看代碼實現。

  • HTTPServer
    該類就是實現了一個非阻塞、單線程模式的http server端功能。該類繼承自TCPServer,因爲http協議是在tcp傳輸協議之上的,所以繼承TCPServer是自然的,這也隱式說明,TCPServer的三種實例化方法,也是適用於HTTPServer;而http協議之上的應用實現,一般是web服務功能,在Tornado的實現,從HTTPServer的角度來看,所謂的一個服務功能,就是指繼承自HTTPServerConnectionDelegate類的子類實現(或者,爲了向後兼容,也可以是一個以HTTPServerRequest類實例爲參數的一個回調函數),這個服務功能就是在HTTPServer實例化時,必需要傳遞的一個參數。到時在收到http請求時,HTTPServer會把相應的http請求,傳遞給這個服務功能去進一步處理。

  • HTTP1ServerConnection
    代表着一個http連接的服務端。由HTTPServer調用生成相應的實例。可以這麼理解,一個HTTPServer,指的是整體概念上的http服務器實現,而對於每個具體的連接,在其中衝當服務端實現的就是由HTTP1ServerConnection實現;因此一個 HTTPServer可以根據實際的客戶端連接,不斷生成HTTP1ServerConnection實例。

  • HTTPServerConnectionDelegate
    接口定義,約定了start_request接口和on_close接口去進一步處理相應的http請求。

  • HTTPMessageDelegate
    接口定義類,用於實現對http請求或者響應消息的處理。

  • HTTPConnection
    用於響應http請求的類接口定義。約定有write_headers接口,用於寫http頭域信息;write接口,用於寫http的body內容;finish接口,提示全部數據已經寫完成。(這裏注意不要被名字給騙了,這隻個定義響應相關的接口,而不是定義http協議連接的實現,http1.x協議相關的實現定義在HTTP1Connection類中)

  • HTTP1Connection
    繼承自HTTPConnection,這裏定義實現了http1.x 協議的邏輯實現。實現了讀取http請求,並提交給相應的消息處理器(HTTPMessageDelegate類的實例)進行消息處理;以及由上層調用相關的寫方法回覆響應。

除此之外,還有相關的其它類,這裏就不作一一介紹了。個人覺得比較容易混淆的,是這些類的功能實現。

簡單總結一下:
1、HTTPServer是http服務端整體概念上的實現,實例化時,需要提供一個HTTPServerConnectionDelegate子類實例,比如後續應用層上的 Application 對象;
2、HTTPServer對於每一個客戶端連接,會生成一個HTTP1ServerConnection實例,該實例代表着一個連接交互中的具體的http服務端的角色;
3、對於一次http連接,具體的交互實現由HTTP1Connection類來完成。該類會讀取相應的http請求消息,傳遞給HTTPMessageDelegate類的對象去處理,併發送相應的響應消息。

示意圖如下(綠色表示類繼承、深藍色表示類接口繼承實現、黃色表示類相關關係)
在這裏插入圖片描述

三、HTTPServer類分析

3.1 繼承關係
class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate)

從代碼可以很直觀看出,HTTPServer的類繼承關係。
繼承自TCPServer類,從而由TCPServer類來完成傳輸層相應的工作,不過從之前的TCPServer類的分析中,我們知道,對於繼承自TCPServer類的子類,必須實現TCPServer約定的handle_stream處理方法;
繼承自Configurable類,因此真正的初始化實現,在initialize方法中實現;
繼承自HTTPServerConnectionDelegate接口類,因此需要實現該接口類約定的接口,start_request方法和on_close方法。其中start_request方法在一個請求消息需要開始處理時被調用,其第一個參數server_conn表示傳輸層意義的TCP的長連接;而第二個參數request_conn表示http協議概念上的一次請求與響應的交互連接;而on_close方法在tcp連接關閉時,被調用。

3.2 handle_stream方法實現

實現TCPServer類的缺省接口,實現對於特定網絡連接的異步處理實現

def handle_stream(self, stream: iostream.IOStream, address: Tuple) -> None:
    # 保存本次http連接的相關上下文信息,說白了,就是保留一些信息內容,然後放在一個類裏面。
    context = _HTTPRequestContext(
        stream, address, self.protocol, self.trusted_downstream
    )
    # 生成一個服務端處理實現實例
    conn = HTTP1ServerConnection(stream, self.conn_params, context)
    self._connections.add(conn)
    # 該服務端實例啓動服務,需要注意的是,這裏是異步啓動的,在start_serving內部,是生成了一個協程對象,
    # 提交到stream.io_loop中去異步執行了,從而這裏在調用完了之後就立即返回了。
    # 也就是說,從這裏可以看到HTTPServer的異步網絡實現,每個連接對應生成一個協程對象去執行具體的網絡消息
    # 交互,從而對於HTTPServer來說,對於客戶端連接,實現了非阻塞。
    conn.start_serving(self)

HTTP1ServerConnection.start_serving方法主要是將HTTP1ServerConnection._server_request_loop協程轉換成future對象從而實現協程的異步併發處理。接下來看下_server_request_loop協程的具體工作內容。

async def _server_request_loop(
    self, delegate: httputil.HTTPServerConnectionDelegate
) -> None:
    try:
    	# 循環處理當前連接的HTTP消息
        while True:
            # 實例化一個HTTP1Connection類,前面說了,這個類是http 1.x的具體實現,包含了讀寫交互過程
            conn = HTTP1Connection(self.stream, False, self.params, self.context)
            # 這裏的delegate是之前的HTTPServer類,其實現的start_request方法,就是把當前的連接傳遞給
            # 最開始、初始化HTTPServer時的回調對象上,也就是說傳遞給應用層對象,由應用層對象返回一個
            # HTTPMessageDelegate實例,這個HTTPMessageDelegate代理實例用於後續繼續把相應的請求的具體
            # 內容傳遞給應用層去進一步處理。
            request_delegate = delegate.start_request(self, conn)
            try:
            	# 這裏的conn.read_response最終是調用到HTTP1Connection._read_message方法,這個方法
            	# 也是返回一個協程,而這裏直接使用await方法等待這個協程對象,其作用就是阻塞等待這個協程
            	# 處理完成。在這裏面會有具體的數據讀取處理過程。這裏要理解清楚的是,對於HTTPServer來說
            	# _server_request_loop方法是以立即返回的,協程的運行是提交到io loop循環中去的;
            	# 而對於每個具體的_server_request_loop的協程對象內部,是阻塞執行每一個消息交互的。
                ret = await conn.read_response(request_delegate)
            except:
            	# 異常的處理代碼就不貼出來了
            	pass
            if not ret:
                return
            await asyncio.sleep(0)
    finally:
        delegate.on_close(self)

ok,順着這個邏輯,我們繼續查看HTTP1Connection.read_response內部實現。在HTTP1Connection.read_response內部,重點是看其返回的HTTP1Connection._read_message的協程對象,在這個協程裏面完成數據的讀取和傳遞給HTTPMessageDelegate對象,即前面的request_delegate參數,從而實現把讀取提交給上層應用層去進行詳細的處理。因爲這個協程對象代碼比較長,我就截取重點說明。
首先要知道的是HTTP請求信息由3部分組成:
請求方法URI協議/版本
請求頭(Request Header)
請求正文

# 這裏就可以看到了,從IOStream的read_until_regex方法進行讀取http直到請求頭結束,在請求頭和請求正文
# 有一個空行,這個是http協議規定的。從之前的IOStream類分析,可以知道,這裏返回的是一個future對象,
# 這個future對象的結果就是讀取到的數據。
header_future = self.stream.read_until_regex(
    b"\r?\n\r?\n", max_bytes=self.params.max_header_size
)
# 所以這裏只是區分要不要設置超時等待讀取數據,並且把數據保存在header_data中
if self.params.header_timeout is None:
    header_data = await header_future
else:
    try:
        header_data = await gen.with_timeout(
            self.stream.io_loop.time() + self.params.header_timeout,
            header_future,
            quiet_exceptions=iostream.StreamClosedError,
        )
    except gen.TimeoutError:
        self.close()
        return False

獲取到請求頭相關信息後,解析成首行和頭部對象HTTPHeaders

start_line_str, headers = self._parse_headers(header_data)
...
with _ExceptionLoggingContext(app_log):
     # 這裏把首行信息和頭部信息提交給上層去處理,這個delegate就是前面的HTTPMessageDelegate對象,
     # 即request_delegate參數
    header_recv_future = delegate.headers_received(start_line, headers)
    if header_recv_future is not None:
        await header_recv_future

然後接着讀取body正文,流程也跟讀取頭部差不多,只是HTTP1Connection定義了一個_read_body方法而已,最終依據頭部參數信息,可能進一步區分調用哪個讀取方法;而不管是self._read_fixed_body還是self._read_chunked_body,還是self._read_body_until_close,都是self.stream.read_bytes進行數據讀取,然後通過delegate.data_received(body)傳遞給上層處理。

if content_length is not None:
    return self._read_fixed_body(content_length, delegate)
if headers.get("Transfer-Encoding", "").lower() == "chunked":
    return self._read_chunked_body(delegate)
if self.is_client:
    return self._read_body_until_close(delegate)

到這裏爲止,對於http請求的消息內容是都已經提交給上層應用層了,那麼上層可以依據邏輯實現,生成相應的響應發送給客戶端。還記得HTTP1Connection是繼承自HTTPConnection,而HTTPConnection是定義寫操作的接口嗎?是的。HTTP1Connection需要實現HTTPConnection的寫操作接口:write_headers,write,finish,具體寫實現細節,自行看下代碼就可以了,需要注意的是在前面的讀取消息內容後,如果本連接沒有被脫離(脫離就是指失去對IOStream對象的直接控制,即被調用了detach方法,這個IOStream對象變成由上層來直接管理)或者異步寫還沒有完成,則原來的_read_message對象,會阻塞等待寫操作完成,即await self._finish_future。

那麼上層爲什麼能調用HTTP1Connection對象的這些寫接口呢?原因是在前面的_server_request_loop實現中,通過request_delegate = delegate.start_request(self, conn),即HTTPServer.start_request方法,這個conn對象(即HTTP1Connection對象,是直接傳遞到上層的),從而上層能保存這個對象,進而能調用到HTTP1Connection的寫操作接口。

四、總結HTTPServer承上啓下的實現

HTTPServer直接繼承自TCPServer,並且實現TCPServer.handle_stream方法,從而能處理跟客戶端的連接,這就是啓下;而HTTPServer在初始化時,接受了一個回調對象,這個對象一般是上層應用層對象,在通過HTTPServer.start_request被調用時,會把相應的http連接對象(一個表示長連接的HTTP1ServerConnection對象,一個表示單次http協議交互的HTTP1Connection對象)傳給上層回調對象,而上層回調對象則是返回一個HTTPMessageDelegate對象,用於後續的http請求消息的傳遞,進而上層能相應的響應該消息。

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