Tornado1.0源碼分析-HTTP Servers

#HTTP Servers #ios

做者:MetalBug
時間:2015-03-01
出處:http://my.oschina.net/u/247728/blog
聲明:版權全部,侵犯必究
  • tornado.httpserver `— Non-blocking HTTP server
  • tornado.httputil — Manipulate HTTP headers and URLs

##1.httpserver##服務器

###1.1HTTPServer### HTTPServer是一個非阻塞的HTTP服務器。 在內部使用IOLoop對socket事件進行讀寫,由於IOLoop基於epoll,因此保證了Tornado的高效。cookie

HTTPServer使用:數據結構

  1. 定義對client socket的回調函數,初始化HTTPServerapp

  2. 使用HTTPServer.bind(port)監聽對應端口。異步

  3. 使用HTTPServer.start()開始運行服務器。socket

    http_server = httpserver.HTTPServer(handle_request)
    http_server.bind(8888)
    http_server.start()
    ioloop.IOLoop.instance().start()

如下是HTTPServer的大致處理過程: Tornado-HTTPServer-流程圖函數

####內部實現-數據結構#### self.request_callback爲對client socket的回調函數 self.socket爲listen scoket self.io_loop爲綁定的IOLooptornado

####內部實現-主要函數####工具

HTTPServer.start()中,會根據CPU的核數建立對應的進程,在每一個進程中有本身的IOLoop,由於是進程,因此並無數據競爭的問題。

for i in range(num_processes):
    if os.fork() == 0:
        self.io_loop = ioloop.IOLoop.instance()
        self.io_loop.add_handler(self._socket.fileno(), 
                    self._handle_events,ioloop.IOLoop.READ)
        return

能夠看到,IOLoop監視了HTTPServer的listen socket的READ事件,使用_handle_events回調函數。在這裏,監視的是socket的accept(),對每一個鏈接上來的client socket進行處理。

def _handle_events(self, fd, events):
    while True:
        try:
            connection, address = self._socket.accept()
        except socket.error, e:
            if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
                return
            raise
        #####
        try:
            stream = iostream.IOStream(connection, io_loop=self.io_loop)
            HTTPConnection(stream, address, self.request_callback,
                           self.no_keep_alive, self.xheaders)
        except:
            logging.error("Error in connection callback", exc_info=True)

從代碼能夠獲得,對於新鏈接的client socket,HTTPServer使用IOStream進行包裝,而後傳遞給了HTTPConnectionHTTPConnection對該鏈接進行處理。

###1.2HTTPConnection### HTTPConnection用於處理HTTP鏈接的client,它會解析HTTP head和body,並在得到請求時,生成一個HTTPRequest,執行咱們的_request_callback,直到鏈接關閉。若是HTTP鏈接爲keep-alive,則繼續以上流程。

如下是HTTPConnection的大致執行流程: Tornado-HTTPConnection-流程圖

####內部實現-數據結構#### self.request_callback爲對client socket的回調函數 self.stream爲包裝client的IOStream

####內部實現-主要函數#### 對於_on_headers,_on_request_body_parse_mime_body,都是根據HTTP協議進行解析,這裏針對的是接受到的數據,最終將一個request中的數據用一個HTTPRequest表示。 而對於發送數據,由於發送數據是主動的,而接受數據是被動的,因此發送數據相對更難。

對於HTTPConnection, 其發送數據內部調用的是IOStream.write函數

def write(self, chunk):
    assert self._request, "Request closed"
    if not self.stream.closed():
        self.stream.write(chunk, self._on_write_complete)

能夠看到,這裏HTTPConnection直接將發送數據放到IOStream的write_buffer,並開始關注write事件,在IOStream_handle_write中將數據發送完成。完成發送數據後,會調用_on_write_complete用於處理request的關閉。

def _on_write_complete(self):
    if self._request_finished:
        self._finish_request()

對於self._request_finished初始化爲False,在HTTPConnection.finish()中被置爲True,用於標識request的結束。

當一次request結束以後,會根據請求的類型,是否爲keep-alive從而決定是否關閉鏈接仍是繼續下一個request的解析和處理。

def _finish_request(self):
    if self.no_keep_alive:
        disconnect = True
    else:
        connection_header = self._request.headers.get("Connection")
        if self._request.supports_http_1_1():
            disconnect = connection_header == "close"
        elif ("Content-Length" in self._request.headers
                or self._request.method in ("HEAD", "GET")):
            disconnect = connection_header != "Keep-Alive"
        else:
            disconnect = True
    self._request = None
    self._request_finished = False
    if disconnect:
        self.stream.close()
        return
    self.stream.read_until("\r\n\r\n", self._on_headers)

####內部實現-實現細節#### IOStream中的_handle_write的實現,是反覆調用write函數發送數據。可是在實際中,若是第一次沒有可以發送徹底部數據時,第二次調用write函數大部分會返回EAGAIN。因此在這裏的IOstream._handle_write實現能夠優化。

###1.3HTTPRequest### HTTPRequest是對一次HTTP請求的包裝,更具請求接受到的數據進行解析,提供write和finish的接口。 HTTPRequest只是一個簡單的用於暴露給用戶使用的類,其內部的函數都是HTTPConnection函數的代理。

####內部實現-數據結構####

self.connection即爲該請求對應的鏈接,類型爲HTTPConnection

##2.httputil## httputils包含了httpclienthttpserver共享的工具類。

###2.1.1HTTPHeader### HTTPHeaders繼承了dict,用於表示HTTP頭部中各個key及其對應內容。

>> h.add("Set-Cookie", "A=B")
>> h.add("Set-Cookie", "C=D")
>> h["set-cookie"]
'A=B,C=D'
>> h.get_list("set-cookie")
['A=B', 'C=D']

####內部實現-數據結構 #### HTTPHeaders內部使用拼接字符串的方式實現了multiple values per key.

def __init__(self, *args, **kwargs):
    dict.__init__(self)
    self._as_list = {}
    self.update(*args, **kwargs)

_as_list是dict,一個key對應一個list

def add(self, name, value):
   norm_name = HTTPHeaders._normalize_name(name)
    if norm_name in self:
        dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
        self._as_list[norm_name].append(value) 
    else:
        self[norm_name] = value

HTTPHeaders在內部是維護了一個key對應一個list的結構,從而使用get_list可以返回一個list,這樣形成了數據冗餘,固然僅僅對於處理HTTP的頭部這種小數據量而言,差異並不大。 若是要避免冗餘的話,直接使用split函數對拼接而成的字符串進行處理便可。

#總結# 基於IOLoopTornado1.0實現了HTTPServer,一個非阻塞的HTTP服務器,同時利用IOStream實現了異步讀寫,對於讀取數據而後針對HTTP的解析,這裏在讀取的過程當中逐步解析了。 而對於發送數據,這裏有兩個能夠改進的,

  1. HTTPConnectionwrite函數直接調用IOStream完成,而 IOStream中的_handle_write的實現,是反覆調用write函數發送數據。可是在實際中,若是第一次沒有可以發送徹底部數據時,第二次調用write函數大部分會返回EAGAIN。因此在這裏的IOstream._handle_write實現能夠優化。

同時能夠選擇在HTTPConnection嘗試往client socket中寫一次數據,若是可以完成所有數據的發送,而並不使用IOStream進行發送,若是沒有寫完再使用IOStream進行發送數據。固然,若是此時IOStream的write_buffer不爲空,則不能嘗試先嚐試發送,不然會形成時序錯亂。

  1. 關於發送速率並無進行考慮,若是發送數據的速率高於對方接受數據的速率,這會形成數據在本地內存中的堆積,對效率形成影響。在這裏可使用高水位回調和低水位回調進行控制。

相關文章
相關標籤/搜索