python自帶的http模塊詳解
挺久沒寫博客了,因為博主開始了今年另一段美好的實習經(jīng)歷,學習加做項目,時間已排滿;很感謝今年這兩段經(jīng)歷,讓我接觸了golang和python,學習不同語言,可以跳出之前學習c/c++思維的限制,學習golang和python的優(yōu)秀特性以及了解在不同的場景,適用不同的語言;而之前學習linux和c/c++,也使我很快就上手golang和python;
我學習的習慣,除了學習如何使用,還喜歡研究源碼,學習運行機制,這樣用起來才會得心應(yīng)手或者說,使用這些語言或框架,就和平時吃飯睡覺一樣,非常自然;因為最近有接觸到bottle和flask web框架,所以想看下這兩個的源碼,但是這兩個框架是基于python自帶的http,因此就有了這篇文章;
python http簡單例子
python http框架主要有server和handler組成,server主要是用于建立網(wǎng)絡(luò)模型,例如利用epoll監(jiān)聽socket;handler用于處理各個就緒的socket;先來看下python http簡單的使用:
import sys from http.server import HTTPServer,SimpleHTTPRequestHandler ServerClass = HTTPServer HandlerClass = SimpleHTTPRequestHandler if__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監(jiān)聽到有request到來時,就把這個request丟給SimpleHTTPRequestHandler類求處理;ok,了解這些之后,我們開始分別分析下server和handler.
http之server
http模塊的設(shè)計充分利用了面向?qū)ο蟮睦^承多態(tài),因為之前有看了會tfs文件系統(tǒng)的代碼,所以再看python http時,沒那么大的壓力;先給出server的繼承關(guān)系
+------------------+ +------------+| tcpserver基類 | | BaseServer +-------->| 開啟事件循環(huán)監(jiān)聽 | +-----+------+ | 處理客戶端請求 | | +------------------+ v +-----------------+ +------------+| httpserver基類 | | TCPServer +-------->+設(shè)置監(jiān)聽socket | +-----+------+ | 開啟監(jiān)聽 | | +-----------------+ v +------------+ | HTTPServer | +------------+
繼承關(guān)系如上圖所示,其中BaseServer和TCPServer在文件socketserver.py,HTTPServer在http/server.py;我們先看下來BaseServer;
BaseServer
因為BaseServer是所有server的基類,因此BaseServer盡可能抽象出所有server的共性,例如開啟事件監(jiān)聽循環(huán),這就是每個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多路復(fù)用,然后將服務(wù)自身監(jiān)聽的socket注冊到io多路復(fù)用,開啟事件監(jiān)聽,當有客戶端連接時,此時會調(diào)用self._handle_request_noblock()來處理請求;接下來看下這個處理函數(shù)做了啥;
def_handle_request_noblock(self): try: request, client_address = self.get_request() exceptOSError: return ifself.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函數(shù)是一個內(nèi)部函數(shù),首先是接收客戶端連接請求,底層其實是封裝了系統(tǒng)調(diào)用accept函數(shù),然后驗證請求,最后調(diào)用process_request來處理請求;其中g(shù)et_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函數(shù)先是調(diào)用了finish_request來處理一個連接,處理結(jié)束之后,調(diào)用shutdown_request函數(shù)來關(guān)閉這個連接;而finish_request函數(shù)內(nèi)部實例化了一個handler類,并把客戶端的socket和地址傳了進去,說明,handler類在初始化結(jié)束的時候,就完成了請求處理,這個等后續(xù)分析handler時再細看;
以上就是BaseServer所做的事,這個BaseServer不能直接使用,因為有些函數(shù)還沒實現(xiàn),只是作為tcp/udp的抽象層;總結(jié)下:
先是調(diào)用serve_forever開啟事件監(jiān)聽;
然后當有客戶端請求到來時,將請求交給handler處理;
TCPServer
由上述BaseServer抽象出的功能,我們可以知道TCPServer或UDPServer應(yīng)該完成的功能有,初始化監(jiān)聽套接字,并綁定監(jiān)聽,最后當有客戶端請求時,接收這個客戶端;我們來看下代碼
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初始化時先是調(diào)用基類BaseServer的初始化函數(shù),初始化服務(wù)器地址,handler類等,然后初始化自身的監(jiān)聽套接字,最后調(diào)用server_bind綁定套接字,server_activate監(jiān)聽套接字
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還實現(xiàn)了另一個函數(shù),那就是接收客戶端請求,
defget_request(self): returnself.socket.accept()
之前如果有學過linux編程,那么看這些代碼應(yīng)該會覺得很熟悉,因為函數(shù)名和Linux提供的系統(tǒng)調(diào)用名一模一樣,這里也不多說了;
TCPServer其實已經(jīng)把基于tcp的服務(wù)器主體框架搭起來了,因此HTTPServer在繼承TCPServer基礎(chǔ)上,只是重載了server_bind函數(shù),設(shè)置reuse_address等;
ok,這里分析下上述例子程序的開啟過程;
httpd = ServerClass(server_address,HandlerClass)這行代碼在初始化HTTPServer時,主要是調(diào)用基類TCPServer的初始化方法,初始化了監(jiān)聽的套接字,并綁定和監(jiān)聽;
httpd.serve_forever()這行代碼調(diào)用的是基類BaseServer的serve_forever方法,開啟監(jiān)聽循環(huán),等待客戶端的連接;
如果有看過redis或者一些后臺組件的源碼,對這種并發(fā)模型應(yīng)該很熟悉;ok,分析了server之后,接下來看下handler是如何處理客戶端請求的。
http之handler
handler類主要分析tcp層的handler和http應(yīng)用層的handler,tcp層的handler是不能使用的,因為tcp層只負責傳輸字節(jié),但是并不知對于接收到的字節(jié)要如何解析,如何處理等;因此應(yīng)用層協(xié)議如該要使用TCP協(xié)議,必須繼承TCP handler,然后實現(xiàn)handle函數(shù)即可;例如,http層的handler實現(xiàn)handle函數(shù),解析http協(xié)議,處理業(yè)務(wù)請求以及結(jié)果返回給客戶端;先來看下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): pass defhandle(self): pass deffinish(self): pass
之前在看server時,知道處理客戶端請求就是在handler類的初始化函數(shù)中完成;由這個基類初始化函數(shù),我們知道處理請求大概經(jīng)歷三個過程:
- setup對客戶端的socket做一些設(shè)置;
- handle真正處理請求的函數(shù);
- finish關(guān)閉socket讀寫請求;
這個BaseRequestHandler是handler top level 基類,只是抽象出handler整體框架,并沒有實際的處理;我們看下tcp handler,
classStreamRequestHandler(BaseRequestHandler): timeout = None disable_nagle_algorithm = False defsetup(self): self.connection = self.request ifself.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實現(xiàn)了setup和finish函數(shù),setup函數(shù)設(shè)置超時時間,開啟nagle算法以及設(shè)置socket讀寫緩存;finish函數(shù)關(guān)閉socket讀寫;
由上述兩個tcp層的handler可知,要實現(xiàn)一個基于http的服務(wù)器handler,只需要繼承StreamRequestHandler類,并實現(xiàn)handle函數(shù)即可;因此這也是http層handler主要做的事;
http層handler
由之前tcp層handler的介紹,我們知道http層handler在繼承tcp層handler基礎(chǔ)上,主要是實現(xiàn)了handle函數(shù)處理客戶端的請求;還是直接看代碼吧;
defhandle(self): self.close_connection = True self.handle_one_request() whilenotself.close_connection: self.handle_one_request()
這就是BaseHTTPRequestHandler的handle函數(shù),在handle函數(shù)會調(diào)用handle_one_request函數(shù)處理一次請求;默認情況下是短鏈接,因此在執(zhí)行了一次請求之后,就不會進入while循環(huán)在同一個連接上處理下一個請求,但是在handle_one_request函數(shù)內(nèi)部會進行判斷,如果請求頭中的connection為keep_alive或者http版本大于等于1.1,則可以保持長鏈接;接下來看下handle_one_request函數(shù)是如何處理;
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) return ifnotself.raw_requestline: self.close_connection = True return ifnotself.parse_request(): return mname = 'do_'+self.command ifnothasattr(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 = True return
這個handle_one_request執(zhí)行過程如下:
- 先是調(diào)用parse_request解析客戶端http請求內(nèi)容
- 通過"do_"+command構(gòu)造出請求所對于的函數(shù)method
- 調(diào)用method函數(shù),處理業(yè)務(wù)并將response返回給客戶端
這個BaseHTTPRequestHandler是http handler基類,因此也是無法直接使用,因為它沒有定義請求處理函數(shù),即method函數(shù);好在python為我們提供了一個簡單的SimpleHTTPRequestHandler,該類繼承了BaseHTTPRequestHandler,并實現(xiàn)了請求函數(shù);我們看下get函數(shù):
# SimpleHTTPRequestHandler # --------------------------------------------- defdo_GET(self): """Serve a GET request.""" f = self.send_head() iff: try: self.copyfile(f, self.wfile) finally: f.close()
這個get函數(shù)先是調(diào)用do_GET函數(shù)給客戶端返回response頭部,并返回請求的文件,最后調(diào)用copyfile函數(shù)將請求文件通過連接返回給客戶端;
以上就是http模塊最基礎(chǔ)的內(nèi)容,最后,總結(jié)下例子程序handler部分:
- server把請求傳給SimpleHTTPRequestHandler初始化函數(shù);
- SimpleHTTPRequestHandler在初始化部分,對這個客戶端connection進行一些設(shè)置;
- 接著調(diào)用handle函數(shù)處理請求;
- 在handle函數(shù)接著調(diào)用handle_one_request處理請求;
- 在handle_one_request函數(shù)內(nèi)部,解析請求,找到請求處理函數(shù);
- 我之前的訪問屬于get訪問,因此直接調(diào)用do_GET函數(shù)將index.html文件返回給客戶端;
python http模塊到此已經(jīng)分析結(jié)束;不知道大家有沒發(fā)現(xiàn),python自帶的http模塊使用起來不是很方便,因為它是通過請求方法來調(diào)用請求函數(shù),這樣當同一方法調(diào)用次數(shù)非常多時,例如get和post方法,會導致這個請求函數(shù)異常龐大,代碼不好編寫,各種情況判斷;當然SimpleHTTPRequestHandler只是python提供的一個簡單例子而已;
當然,python官方提供了針對http更好用的框架,即wsgi server和wsgi application;接下來文章先分析python自帶的wsgiref模塊以及bottle,后面再分析flask;
相關(guān)文章
python使用os.listdir和os.walk獲得文件的路徑的方法
本篇文章主要介紹了python使用os.listdir和os.walk獲得文件的路徑的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12numpy中以文本的方式存儲以及讀取數(shù)據(jù)方法
今天小編就為大家分享一篇numpy中以文本的方式存儲以及讀取數(shù)據(jù)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06Python cookbook(數(shù)據(jù)結(jié)構(gòu)與算法)篩選及提取序列中元素的方法
這篇文章主要介紹了Python cookbook(數(shù)據(jù)結(jié)構(gòu)與算法)篩選及提取序列中元素的方法,涉及Python列表推導式、生成器表達式及filter()函數(shù)相關(guān)使用技巧,需要的朋友可以參考下2018-03-03python的mysql數(shù)據(jù)庫建立表與插入數(shù)據(jù)操作示例
這篇文章主要介紹了python的mysql數(shù)據(jù)庫建立表與插入數(shù)據(jù)操作,結(jié)合實例形式分析了python操作mysql數(shù)據(jù)庫建立表與插入數(shù)據(jù)相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2019-09-09Python爬蟲實現(xiàn)全國失信被執(zhí)行人名單查詢功能示例
這篇文章主要介紹了Python爬蟲實現(xiàn)全國失信被執(zhí)行人名單查詢功能,涉及Python爬蟲相關(guān)網(wǎng)絡(luò)接口調(diào)用及json數(shù)據(jù)轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2018-05-05