python內建的http模組詳解,python模組詳解
挺久沒寫部落格了,因為博主開始了今年另一段美好的實習經曆,學習加做項目,時間已排滿;很感謝今年這兩段經曆,讓我接觸了golang和python,學習不同語言,可以跳出之前學習c/c++思維的限制,學習golang和python的優秀特性以及瞭解在不同的情境,適用不同的語言;而之前學習linux和c/c++,也使我很快就上手golang和python;
我學習的習慣,除了學習如何使用,還喜歡研究源碼,學習運行機制,這樣用起來才會得心應手或者說,使用這些語言或架構,就和平時吃飯睡覺一樣,非常自然;因為最近有接觸到bottle和flask web架構,所以想看下這兩個的源碼,但是這兩個架構是基於python內建的http,因此就有了這篇文章;
python http簡單例子
python http架構主要有server和handler組成,server主要是用於建立網路模型,例如利用epoll監聽socket;handler用於處理各個就緒的socket;先來看下python http簡單的使用:
import sysfrom http.server import HTTPServer,SimpleHTTPRequestHandlerServerClass = HTTPServerHandlerClass = SimpleHTTPRequestHandlerif__name__ =='__main__': port = int(sys.argv[2]) server_address = (sys.argv[1],port) httpd = ServerClass(server_address,HandlerClass)sa=httpd.socket.getsockname()print("Serving HTTP on",sa[0],"port",sa[1],"...")try: httpd.serve_forever() except KeyboardInterrupt:print("\nKeyboard interrupt received, exiting.") httpd.server_close() sys.exit(0)
運行上述例子,可以得到如下:
python3 myhttp.py 127.0.0.1 9999
此時如果在當前檔案夾建立一個index.html檔案,就可以通過 http://127.0.0.1:9999/index.html 訪問了index.html頁面了。
這個例子的server類用的是HTTPServer,handler類是SimpleHTTPRequestHandler,因此當HTTPServer監聽到有request到來時,就把這個request丟給SimpleHTTPRequestHandler類求處理;ok,瞭解這些之後,我們開始分別分析下server和handler.
http之server
http模組的設計充分利用了物件導向的繼承多態,因為之前有看了會tfs檔案系統的代碼,所以再看python http時,沒那麼大的壓力;先給出server的繼承關係
+------------------++------------+| tcpserver基類 || BaseServer +-------->| 開啟事件迴圈監聽 |+-----+------+ | 處理用戶端請求 | | +------------------+ v +-----------------++------------+| httpserver基類 || TCPServer +-------->+設定監聽socket |+-----+------+ | 開啟監聽 | | +-----------------+ v+------------+| HTTPServer | +------------+
繼承關係如所示,其中BaseServer和TCPServer在檔案socketserver.py,HTTPServer在http/server.py;我們先看下來BaseServer;
BaseServer
因為BaseServer是所有server的基類,因此BaseServer儘可能抽象出所有server的共性,例如開啟事件監聽迴圈,這就是每個server的共性,因此這也是BaseServer主要做的使;我們來看下BaseServer主要代碼部分
defserve_forever(self, poll_interval=0.5): self.__is_shut_down.clear()try:with_ServerSelector()asselector: selector.register(self, selectors.EVENT_READ)whilenotself.__shutdown_request: ready = selector.select(poll_interval)ifready: self._handle_request_noblock() self.service_actions()finally: self.__shutdown_request = False self.__is_shut_down.set()
代碼中的selector其實就是封裝了select,poll,epoll等的io多工,然後將服務自身監聽的socket註冊到io多工,開啟事件監聽,當有用戶端串連時,此時會調用self._handle_request_noblock()來處理請求;接下來看下這個處理函數做了啥;
def_handle_request_noblock(self):try: request, client_address = self.get_request()exceptOSError:returnifself.verify_request(request, client_address):try: self.process_request(request, client_address)except: self.handle_error(request, client_address) self.shutdown_request(request)else: self.shutdown_request(request)
_handle_request_noblock函數是一個內建函式,首先是接收用戶端串連請求,底層其實是封裝了系統調用accept函數,然後驗證請求,最後調用process_request來處理請求;其中get_request是屬於子類的方法,因為tcp和udp接收用戶端請求是不一樣的(tcp有串連,udp無串連)
我們接下來再看下process_request具體做了什麼;
defprocess_request(self, request, client_address): self.finish_request(request, client_address) self.shutdown_request(request)# -------------------------------------------------deffinish_request(self, request, client_address): self.RequestHandlerClass(request, client_address, self)defshutdown_request(self, request): self.close_request(request)
process_request函數先是調用了finish_request來處理一個串連,處理結束之後,調用shutdown_request函數來關閉這個串連;而finish_request函數內部執行個體化了一個handler類,並把用戶端的socket和地址傳了進去,說明,handler類在初始化結束的時候,就完成了請求處理,這個等後續分析handler時再細看;
以上就是BaseServer所做的事,這個BaseServer不能直接使用,因為有些函數還沒實現,只是作為tcp/udp的抽象層;總結下:
先是調用serve_forever開啟事件監聽;
然後當有用戶端請求到來時,將請求交給handler處理;
TCPServer
由上述BaseServer抽象出的功能,我們可以知道TCPServer或UDPServer應該完成的功能有,初始化監聽通訊端,並綁定監聽,最後當有用戶端請求時,接收這個用戶端;我們來看下代碼
BaseServer==>def__init__(self, server_address, RequestHandlerClass):"""Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False#--------------------------------------------------------------------------------TCPServer==>def__init__(self, server_address, RequestHandlerClass, bind_and_activate=True): BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type)ifbind_and_activate:try: self.server_bind() self.server_activate()except: self.server_close()raise
TCPServer初始化時先是調用基類BaseServer的初始化函數,初始化伺服器位址,handler類等,然後初始化自身的監聽通訊端,最後調用server_bind綁定通訊端,server_activate監聽通訊端
defserver_bind(self):ifself.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname()defserver_activate(self): self.socket.listen(self.request_queue_size)
TCPServer還實現了另一個函數,那就是接收用戶端請求,
defget_request(self):returnself.socket.accept()
之前如果有學過linux編程,那麼看這些代碼應該會覺得很熟悉,因為函數名和Linux提供的系統調用名一模一樣,這裡也不多說了;
TCPServer其實已經把基於tcp的伺服器主體架構搭起來了,因此HTTPServer在繼承TCPServer基礎上,只是重載了server_bind函數,設定reuse_address等;
ok,這裡分析下上述例子程式的開啟過程;
httpd = ServerClass(server_address,HandlerClass)這行代碼在初始化HTTPServer時,主要是調用基類TCPServer的初始化方法,初始化了監聽的通訊端,並綁定和監聽;
httpd.serve_forever()這行代碼調用的是基類BaseServer的serve_forever方法,開啟監聽迴圈,等待用戶端的串連;
如果有看過redis或者一些後台組件的源碼,對這種並行存取模型應該很熟悉;ok,分析了server之後,接下來看下handler是如何處理用戶端請求的。
http之handler
handler類主要分析tcp層的handler和http應用程式層的handler,tcp層的handler是不能使用的,因為tcp層只負責傳輸位元組,但是並不知對於接收到的位元組要如何解析,如何處理等;因此應用程式層協議如該要使用TCP協議,必須繼承TCP handler,然後實現handle函數即可;例如,http層的handler實現handle函數,解析http協議,處理業務請求以及結果返回給用戶端;先來看下tcp層的handler
tcp層handler
tcp層handler主要有BaseRequestHandler和StreamRequestHandler(都在socketserver.py檔案),先看下BaseRequestHandler代碼,
classBaseRequestHandler: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()defsetup(self):passdefhandle(self):passdeffinish(self):pass
之前在看server時,知道處理用戶端請求就是在handler類的初始化函數中完成;由這個基類初始化函數,我們知道處理請求大概經曆三個過程:
- setup對用戶端的socket做一些設定;
- handle真正處理請求的函數;
- finish關閉socket讀寫請求;
這個BaseRequestHandler是handler top level 基類,只是抽象出handler整體架構,並沒有實際的處理;我們看下tcp handler,
classStreamRequestHandler(BaseRequestHandler): timeout = None disable_nagle_algorithm = Falsedefsetup(self): self.connection = self.requestifself.timeoutisnotNone: self.connection.settimeout(self.timeout)ifself.disable_nagle_algorithm: self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize)deffinish(self):ifnotself.wfile.closed:try: self.wfile.flush()exceptsocket.error:pass self.wfile.close() self.rfile.close()
tcp handler實現了setup和finish函數,setup函數設定逾時時間,開啟nagle演算法以及設定socket讀寫緩衝;finish函數關閉socket讀寫;
由上述兩個tcp層的handler可知,要實現一個基於http的伺服器handler,只需要繼承StreamRequestHandler類,並實現handle函數即可;因此這也是http層handler主要做的事;
http層handler
由之前tcp層handler的介紹,我們知道http層handler在繼承tcp層handler基礎上,主要是實現了handle函數處理用戶端的請求;還是直接看代碼吧;
defhandle(self): self.close_connection = True self.handle_one_request()whilenotself.close_connection: self.handle_one_request()
這就是BaseHTTPRequestHandler的handle函數,在handle函數會調用handle_one_request函數處理一次請求;預設情況下是短連結,因此在執行了一次請求之後,就不會進入while迴圈在同一個串連上處理下一個請求,但是在handle_one_request函數內部會進行判斷,如果要求標頭中的connection為keep_alive或者http版本大於等於1.1,則可以保持長連結;接下來看下handle_one_request函數是如何處理;
defhandle_one_request(self):try:self.raw_requestline =self.rfile.readline(65537)iflen(self.raw_requestline) >65536:self.requestline =''self.request_version =''self.command =''self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)returnifnotself.raw_requestline:self.close_connection = Truereturnifnotself.parse_request():return mname = 'do_'+self.commandifnothasattr(self, mname):self.send_error( HTTPStatus.NOT_IMPLEMENTED,"Unsupported method (%r)"%self.command)return method = getattr(self, mname) method()self.wfile.flush() except socket.timeout as e:self.log_error("Request timed out: %r", e)self.close_connection = Truereturn
這個handle_one_request執行過程如下:
- 先是調用parse_request解析用戶端http請求內容
- 通過"do_"+command構造出請求所對於的函數method
- 調用method函數,處理業務並將response返回給用戶端
這個BaseHTTPRequestHandler是http handler基類,因此也是無法直接使用,因為它沒有定義請求處理函數,即method函數;好在python為我們提供了一個簡單的SimpleHTTPRequestHandler,該類繼承了BaseHTTPRequestHandler,並實現了請求函數;我們看下get函數:
# SimpleHTTPRequestHandler# ---------------------------------------------defdo_GET(self):"""Serve a GET request.""" f = self.send_head()iff:try: self.copyfile(f, self.wfile)finally: f.close()
這個get函數先是調用do_GET函數給用戶端返回response頭部,並返回請求的檔案,最後調用copyfile函數將請求檔案通過串連返回給用戶端;
以上就是http模組最基礎的內容,最後,總結下例子程式handler部分:
- server把請求傳給SimpleHTTPRequestHandler初始化函數;
- SimpleHTTPRequestHandler在初始化部分,對這個用戶端connection進行一些設定;
- 接著調用handle函數處理請求;
- 在handle函數接著調用handle_one_request處理請求;
- 在handle_one_request函數內部,解析請求,找到請求處理函數;
- 我之前的訪問屬於get訪問,因此直接調用do_GET函數將index.html檔案返回給用戶端;
python http模組到此已經分析結束;不知道大家有沒發現,python內建的http模組使用起來不是很方便,因為它是通過要求方法來調用請求函數,這樣當同一方法調用次數非常多時,例如get和post方法,會導致這個請求函數異常龐大,代碼不好編寫,各種情況判斷;當然SimpleHTTPRequestHandler只是python提供的一個簡單例子而已;
當然,python官方提供了針對http更好用的架構,即wsgi server和wsgi application;接下來文章先分析python內建的wsgiref模組以及bottle,後面再分析flask;