#HTTP Servers #ios
做者:MetalBug 時間:2015-03-01 出處:http://my.oschina.net/u/247728/blog 聲明:版權全部,侵犯必究
tornado.httpserver
`— Non-blocking HTTP servertornado.httputil
— Manipulate HTTP headers and URLs##1.httpserver##服務器
###1.1HTTPServer### HTTPServer
是一個非阻塞的HTTP服務器。 在內部使用IOLoop
對socket事件進行讀寫,由於IOLoop
基於epoll
,因此保證了Tornado
的高效。cookie
HTTPServer使用:數據結構
定義對client socket的回調函數,初始化HTTPServer
。app
使用HTTPServer.bind(port)
監聽對應端口。異步
使用HTTPServer.start()
開始運行服務器。socket
http_server = httpserver.HTTPServer(handle_request) http_server.bind(8888) http_server.start() ioloop.IOLoop.instance().start()
如下是HTTPServer的大致處理過程: 函數
####內部實現-數據結構#### self.request_callback
爲對client socket的回調函數 self.socket
爲listen scoket self.io_loop
爲綁定的IOLoop
tornado
####內部實現-主要函數####工具
在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
進行包裝,而後傳遞給了HTTPConnection
,HTTPConnection
對該鏈接進行處理。
###1.2HTTPConnection### HTTPConnection
用於處理HTTP鏈接的client,它會解析HTTP head和body,並在得到請求時,生成一個HTTPRequest
,執行咱們的_request_callback
,直到鏈接關閉。若是HTTP鏈接爲keep-alive,則繼續以上流程。
如下是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
包含了httpclient
和httpserver
共享的工具類。
###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函數對拼接而成的字符串進行處理便可。
#總結# 基於IOLoop
,Tornado1.0
實現了HTTPServer
,一個非阻塞的HTTP服務器,同時利用IOStream
實現了異步讀寫,對於讀取數據而後針對HTTP的解析,這裏在讀取的過程當中逐步解析了。 而對於發送數據,這裏有兩個能夠改進的,
HTTPConnection
的write
函數直接調用IOStream完成,而IOStream
中的_handle_write
的實現,是反覆調用write函數發送數據。可是在實際中,若是第一次沒有可以發送徹底部數據時,第二次調用write函數大部分會返回EAGAIN
。因此在這裏的IOstream._handle_write
實現能夠優化。
同時能夠選擇在
HTTPConnection
嘗試往client socket中寫一次數據,若是可以完成所有數據的發送,而並不使用IOStream
進行發送,若是沒有寫完再使用IOStream進行發送數據。固然,若是此時IOStream
的write_buffer不爲空,則不能嘗試先嚐試發送,不然會形成時序錯亂。
關於發送速率並無進行考慮,若是發送數據的速率高於對方接受數據的速率,這會形成數據在本地內存中的堆積,對效率形成影響。在這裏可使用高水位回調和低水位回調進行控制。