python內(nèi)置HTTP Server如何實現(xiàn)及原理解析
應(yīng)用案例
from http.server import HTTPServer, BaseHTTPRequestHandler IP = '127.0.0.1' PORT = 8000 class Handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() message = "Hello, World!" self.wfile.write(bytes(message, "utf8")) with HTTPServer((IP, PORT), Handler) as httpd: print("serving at port", PORT) httpd.serve_forever()
以上是使用內(nèi)置模塊 http.server
實現(xiàn)的一個最簡單的 http 服務(wù)器,能處理 http GET 請求。
python 內(nèi)置的 http server 主要集中在兩個代碼文件上,分別是 socketserver.py
和 http/server.py
。socketserver.py
提供 socket 通信能力的 Server 封裝并預(yù)留了用戶自定義請求處理的接口;http/server.py
基于前者做進一步封裝,用得比較多的是 HTTP 的封裝。
從開頭的例子出發(fā)閱讀代碼(python 3.10.1),大致梳理出以下代碼結(jié)構(gòu),圖畫得很隨意無規(guī)范可言,只是為了更具象化解釋。
問題一:實現(xiàn)一個 HTTP 服務(wù)器大致需要什么要素
先看圖 1,左邊 BaseServer
一列是類,從上往下是父類到子類;右邊 server_forever()
一列是方法,從上往下是逐步深入的調(diào)用鏈。
從父類到子類 主線流程 +----------------+ +------------------+ | | | | | BaseServer +--------------------->| serve_forever() | | | | | +--------+-------+ +--------=+--------+ | | | | | | V V +----------------+ +----------------------------+ | | | | | TCPServer | | _handle_request_noblock() | | | | | +--------+-------+ +-------------+--------------+ | | +-----------+------------+ | | | | V V V +----------------+ +----------------+ +------------------+ | | | | | | | HTTPServer | | UDPServer | | process_request()| | | | | | | +----------------+ +----------------+ +---------+--------+ | | | V +------------------+ | | | finish_request() | | | +------------------+
圖 1
例子中使用了 HTTPServer
這個類,字面意思,這個類就是一個 HTTP 服務(wù)器,順著繼承鏈看到 HTTPServer
是 TCPServer
的子類,符合 HTTP 報文是基于 TCP 協(xié)議傳輸?shù)恼J知,HTTPServer
類其實沒什么內(nèi)容,代碼如下:
class HTTPServer(socketserver.TCPServer): allow_reuse_address = 1 # Seems to make sense in testing environment def server_bind(self): """Override server_bind to store the server name.""" socketserver.TCPServer.server_bind(self) host, port = self.server_address[:2] self.server_name = socket.getfqdn(host) self.server_port = port
TCPServer
的源碼實現(xiàn)得益于父類的預(yù)留接口,只需要 TCP socket 走一遍 bind
、listen
、accept
、close
流程(子類 UDPServer
同理)。
重點關(guān)注 BaseServer
,這里是網(wǎng)絡(luò)請求處理核心流程的實現(xiàn),文章最開頭的例子中 serve_forever()
這個入口方法就是在此類被實現(xiàn),我在源碼上加了些簡單的注釋:
def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: # XXX: Consider using another file descriptor or connecting to the # socket to wake this up instead of polling. Polling reduces our # responsiveness to a shutdown request and wastes cpu at all other # times. with _ServerSelector() as selector: selector.register(self, selectors.EVENT_READ) # 注冊Server描述符并監(jiān)聽I/O讀事件 while not self.__shutdown_request: ready = selector.select(poll_interval) # 超時時長poll_interval避免長時間阻塞,在while循環(huán)下實現(xiàn)輪詢 # bpo-35017: shutdown() called during select(), exit immediately. if self.__shutdown_request: break if ready: self._handle_request_noblock() # 請求過來,I/O讀事件準(zhǔn)備好,開始處理請求 self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set()
從 _handle_request_noblock()
中看到,一個網(wǎng)絡(luò)請求的處理流程無非就是 verify_request()
、process_request()
、shoutdown_request()
加上些許異常處理邏輯,比較簡明。在 finish_request()
中出現(xiàn) RequestHandlerClass
的類對象創(chuàng)建,這里其實就是用戶自定義的 RequestHandler(在 BaseServer
的 __int__()
中被初始化)。源碼如下,較好理解:
def _handle_request_noblock(self): """Handle one request, without blocking. I assume that selector.select() has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). """ try: request, client_address = self.get_request() except OSError: return if self.verify_request(request, client_address): # 從這里開始就是網(wǎng)絡(luò)請求的處理流程 try: self.process_request(request, client_address) except Exception: self.handle_error(request, client_address) self.shutdown_request(request) except: self.shutdown_request(request) raise else: self.shutdown_request(request) def process_request(self, request, client_address): """Call finish_request. Overridden by ForkingMixIn and ThreadingMixIn. """ self.finish_request(request, client_address) self.shutdown_request(request) def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request): """Called to shutdown and close an individual request.""" self.close_request(request)
小結(jié):要實現(xiàn)一個 HTTP 服務(wù)器,需要包含 TCP socket 實現(xiàn),網(wǎng)絡(luò)請求流程大致抽象為 verify_request()
、process_request()
、shoutdown_request()
。如果考慮支持用戶自定義請求處理,還需要預(yù)留接口提供擴展性。當(dāng)然如何要支持處理 HTTP 協(xié)議,還需要具備解析 HTTP 報文的能力,下文繼續(xù)探討。
問題二:python 內(nèi)置的 HTTP Server 是怎么實現(xiàn)的
前文介紹了內(nèi)置一個網(wǎng)絡(luò)請求的處理流程(等價于 HTTP Server 的運行流程),一定程度上解釋了本節(jié)的問題,但欠缺一點細節(jié),沒有體現(xiàn) HTTP 報文的解析邏輯在哪里實現(xiàn)。其實內(nèi)置的 HTTP Server 的把 HTTP 協(xié)議解析的工作解耦出去,單獨做成 BaseHTTPRequestHandler
類,這樣允許用戶自行實現(xiàn)任意應(yīng)用層的協(xié)議解析工作,參考下面圖 2:
+----------------------+ +----------------+ | | | | | BaseRequestHandler +------->| __init__() | | | | | +-----------+----------+ +----------------+ | | | +----------------+ | | | V +--->| setup() | +----------------------+ | | | | | | +----------------+ | StreamRequestHandler +---+ | | | +-----------+----------+ | +----------------+ | | | | | +----> finish() | V | | +------------------------+ +----------------------+ +----------------+ | | | | |SimpleHTTPRequestHandler|<---+BaseHTTPRequestHandler| | | | | +------------------------+ +-----------+----------+ | | | V +------------------+ | | | handler() | | | +---------+--------+ +----------------+ | | | | +--->| parse_request()| | | | | V | +----------------+ +----------------------+ | | | | | handler_one_request()+---+ | | | +----------------+ +----------------------+ | | | +--->| do_XXX() | | | +----------------+
圖 2
圖 2 中,但凡帶括號的都是方法,不帶括號的是類,從上往下也是父類到子類。本著代碼最大化復(fù)用的原則,父類 BaseRequestHandler
的 __init__()
中將工作流程確定下來,分別是 setup()
、handler()
、finish()
的先后調(diào)用順序。setup()
和 finish()
在子類 StreamRequestHandler
被實現(xiàn),最后在 BaseHTTPRequestHandler
類中實現(xiàn) HTTP 協(xié)議解析功能,以及用 HTTP method 來決定調(diào)用哪個用戶自定義的 do_XXX()
方法,如 do_GET()
、do_POST()
等。代碼如下:
class BaseRequestHandler: """Base class for request handler classes. ...... """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass class StreamRequestHandler(BaseRequestHandler): """Define self.rfile and self.wfile for stream sockets.""" # 省略代碼 def setup(self): # 設(shè)置鏈接超時時長、nagle算法、讀寫緩沖區(qū) self.connection = self.request if self.timeout is not None: self.connection.settimeout(self.timeout) if self.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) if self.wbufsize == 0: self.wfile = _SocketWriter(self.connection) else: self.wfile = self.connection.makefile('wb', self.wbufsize) def finish(self): if not self.wfile.closed: try: self.wfile.flush() except socket.error: # A final socket error may have occurred here, such as # the local error ECONNABORTED. pass self.wfile.close() self.rfile.close()
HTTP 協(xié)議解析關(guān)注 parse_request()
方法,由于代碼較多不單獨貼過來,思路如下:
- 解析 HTTP 協(xié)議版本號,確定版本解析是否支持(1.1 <= version < 2.0)
- 獲取 HTTP method
- 解析 HTTP header 解析完 HTTP 協(xié)議后,根據(jù)所獲取的 HTTP method,調(diào)用用戶自定義的對應(yīng)方法,至此結(jié)束。
總結(jié)
python 內(nèi)置的 HTTP Server 實現(xiàn)比較簡潔,功能相對簡單。如果要自行從零實現(xiàn)一個 HTTP Server,設(shè)計上參考 python 的實現(xiàn),應(yīng)該具備以下要素:
- TCP socket 通信
- HTTP 協(xié)議的報文解析
- 用戶自定義的 RequestHandler 調(diào)用(設(shè)計上需要引入拓展)
相關(guān)文章
Python+Selenium實現(xiàn)短視頻熱點爬取
隨著短視頻的大火,不僅可以給人們帶來娛樂,還有熱點新聞時事以及各種知識,刷短視頻也逐漸成為了日常生活的一部分。本文將通過Pyhton依托Selenium來爬取短視頻熱點,需要的可以參考一下2022-04-04python判斷字符串編碼的簡單實現(xiàn)方法(使用chardet)
這篇文章主要介紹了python判斷字符串編碼的簡單實現(xiàn)方法,涉及chardet模塊的安裝與簡單使用方法,需要的朋友可以參考下2016-07-07LyScript實現(xiàn)Hook改寫MessageBox的方法詳解
LyScript可實現(xiàn)自定義匯編指令的替換功能。用戶可自行編寫匯編指令,將程序中特定的通用函數(shù)進行功能改寫與轉(zhuǎn)向操作,此功能原理是簡單的Hook操作。本文將詳細介紹Hook改寫MessageBox的方法,感興趣的可以了解一下2022-09-09Python使用sys.path查看當(dāng)前的模塊搜索路徑
sys.path 是 Python 中的一個列表,它用于存儲模塊搜索路徑,當(dāng)你使用 import 語句導(dǎo)入一個模塊時,Python 會按照 sys.path 列表中的路徑順序來查找這個模塊,本文給大家介紹了Python使用sys.path查看當(dāng)前的模塊搜索路徑,需要的朋友可以參考下2025-02-02