詳解Python中httptools模塊的使用
如果你用過 FastAPI 的話,那么你一定知道 uvicorn,它是一個(gè)基于 uvloop 和 httptools 實(shí)現(xiàn)的高性能 ASGI 服務(wù)器。
其中 uvloop 采用 Cython 編寫,用于替換 asyncio 中的事件循環(huán),可以讓 asyncio 速度增加 2 到 4 倍。而 httptools 是基于 C 語言實(shí)現(xiàn)的 HTTP 解析器,用來解析 HTTP 請求的。
本次就來聊一聊 httptools 這個(gè)模塊的詳細(xì)用法,至于 uvloop、uvicorn 等相關(guān)內(nèi)容,后續(xù)我會(huì)一點(diǎn)一點(diǎn)補(bǔ)充上去,并從源碼的角度全給說明白(挖了個(gè)坑)。
httptools 是一個(gè) HTTP 解析器,它首先提供了一個(gè) parse_url 函數(shù),用來解析 URL。
import?httptools #?第一個(gè)參數(shù)必須是?bytes?對象 url?=?httptools.parse_url( ????b"http://www.baidu.com" ) #?返回一個(gè)?URL?對象 print(url.__class__) """ <class?'httptools.parser.parser.URL'> """
那么這個(gè) URL 對象有哪些屬性呢?
通過源碼可知,總共有七個(gè)屬性,我們來測試一下。
import?httptools #?第一個(gè)參數(shù)是?bytes?對象 url?=?b"http://satori:123456@www.baidu.com:80/s?wd=koishi#flag" url_obj?=?httptools.parse_url(url) print("協(xié)議:",?url_obj.schema) print("IP:",?url_obj.host) print("端口:",?url_obj.port) print("路徑:",?url_obj.path) print("查詢參數(shù):",?url_obj.query) print("錨點(diǎn):",?url_obj.fragment) print("用戶信息:",?url_obj.userinfo) """ 協(xié)議:?b'http' IP:?b'www.baidu.com' 端口:?80 路徑:?b'/s' 查詢參數(shù):?b'wd=koishi' 錨點(diǎn):?b'flag' 用戶信息:?b'satori:123456' """
比較簡單,如果參數(shù)不符合 URL 的標(biāo)準(zhǔn)格式,那么會(huì)拋出 HttpParserInvalidURLError 錯(cuò)誤。
然后是 HTTP 請求報(bào)文和響應(yīng)報(bào)文的解析,因?yàn)閳?bào)文只是一坨字節(jié)流,需要將它解析成某個(gè) Request 對象或 Response 對象,而 httptools 就是干這件事情的。
首先來看一下報(bào)文格式,請求報(bào)文如下:
接下來是響應(yīng)報(bào)文:
所以無論是請求報(bào)文還是響應(yīng)報(bào)文,都由 起始行 + 請求頭/響應(yīng)頭 + 請求體/響應(yīng)體 組成。而我們在拿到原始的報(bào)文之后,也可以很方便地進(jìn)行解析,從圖中可以看出最后一個(gè) Header 字段和響應(yīng)體之間有兩個(gè)換行,而換行用 \r\n 表示。因此我們只要按照 "\r\n\r\n" 進(jìn)行 split 即可,會(huì)得到一個(gè)數(shù)組,數(shù)組的第二個(gè)元素就是請求體/響應(yīng)體,第一個(gè)元素就是起始行 + 請求頭/響應(yīng)頭。
然后對數(shù)組的第一個(gè)元素按照 "\r\n" 再進(jìn)行 split,又可以得到一個(gè)數(shù)組,該數(shù)組的第一個(gè)元素就是起始行,剩余的元素就是請求頭/響應(yīng)頭。
所以我們在拿到報(bào)文之后,完全可以自己手動(dòng)解析,但 httptools 是用 C 實(shí)現(xiàn)的,所以速度會(huì)快一些,但干的事情是一樣的。下面來看看 httptools 如何解析請求報(bào)文:
from?pprint?import?pprint import?httptools #?請求報(bào)文 request_payload?=?b"""POST?/index?a=1?HTTP/1.1 Host:?localhost:8080 Connection:?keep-alive Content-Length:?26 Cache-Control:?max-age=0 Upgrade-Insecure-Requests:?1 Accept:?text/html Accept-Encoding:?gzip,?deflate,?sdch Cookie:?_octo=GH1.1.1989111283.1493917476;?logged_in=yes {"name":"satori","age":17}""" class?Request: ????""" ????將請求報(bào)文的解析結(jié)果封裝成?Request?對象 ????""" ????def?__init__(self): ????????self.headers?=?{} ????????self.body?=?b"" ????????self.path?=?None ????def?on_url(self,?path:?bytes): ????????self.path?=?path ????def?on_header(self,?name:?bytes,?value:?bytes): ????????self.headers[name]?=?value ????def?on_body(self,?body:?bytes): ????????self.body?=?body #?實(shí)例化?Request?對象 request?=?Request() #?將?request?作為參數(shù)傳到?HttpRequestParser?中 parser?=?httptools.HttpRequestParser(request) #?傳入請求報(bào)文,進(jìn)行解析 parser.feed_data(request_payload) #?獲取?HTTP?版本 print(parser.get_http_version()) """ 1.1 """ #?是否是長鏈接(Connection?指定為?keep-alive) print(parser.should_keep_alive()) """ True """ #?獲取請求方法 print(parser.get_method()) """ b'POST' """ #?以上幾個(gè)都是?HttpRequestParser?對象的方法 #?獲取路徑 print(request.path) """ b'/index?a=1' """ #?獲取請求頭 pprint(request.headers) """ {b'Accept':?b'text/html', ?b'Accept-Encoding':?b'gzip,?deflate,?sdch', ?b'Cache-Control':?b'max-age=0', ?b'Connection':?b'keep-alive', ?b'Content-Length':?b'26', ?b'Cookie':?b'_octo=GH1.1.1989111283.1493917476;?logged_in=yes', ?b'Host':?b'localhost:8080', ?b'Upgrade-Insecure-Requests':?b'1'} """ #?Cookie?也是請求頭的一部分,但在解析的時(shí)候會(huì)單獨(dú)拿出來 #?再解析成一個(gè)字典,然后通過?request.cookies?獲取 #?獲取請求體 print(request.body) """ b'{"name":"satori","age":17}' """
以上就是請求報(bào)文的解析,再來看看響應(yīng)報(bào)文。
from?pprint?import?pprint import?httptools #?響應(yīng)報(bào)文 response_payload?=?b"""HTTP/1.1?200?OK Server:?TornadoServer/6.1 Content-Type:?text/html;?charset=UTF-8 Date:?Sun,?22?May?2022?17:54:11?GMT Content-Length:?21 name:?satori,?age:?17""" class?Response: ????""" ????將響應(yīng)報(bào)文的解析結(jié)果封裝成?Response?對象 ????""" ????def?__init__(self): ????????self.headers?=?{} ????????self.body?=?b"" ????????self.status?=?b"" ????def?on_header(self,?name:?bytes,?value:?bytes): ????????self.headers[name]?=?value ????def?on_body(self,?body:?bytes): ????????self.body?=?body ????def?on_status(self,?status:?bytes): ????????self.status?=?status #?實(shí)例化?Response?對象 response?=?Response() #?將?response?作為參數(shù)傳到?HttpResponseParser?中 parser?=?httptools.HttpResponseParser(response) #?傳入響應(yīng)報(bào)文,進(jìn)行解析 parser.feed_data(response_payload) #?獲取?HTTP?版本 print(parser.get_http_version()) """ 1.1 """ #?是否是長鏈接(不指定?Connection,默認(rèn)為長連接) print(parser.should_keep_alive()) """ True """ #?獲取狀態(tài)碼 print(parser.get_status_code()) """ b'OK' """ #?獲取狀態(tài)碼對應(yīng)的描述 print(response.status) """ b'OK' """ #?獲取響應(yīng)頭 pprint(response.headers) """ {b'Content-Length':?b'21', ?b'Content-Type':?b'text/html;?charset=UTF-8', ?b'Date':?b'Sun,?22?May?2022?17:54:11?GMT', ?b'Server':?b'TornadoServer/6.1'} """ #?獲取響應(yīng)體 print(response.body) """ b'name:?satori,?age:?17' """
以上就是請求報(bào)文和響應(yīng)報(bào)文的解析,但如果你不是手動(dòng)發(fā)送 TCP 請求的話,那么該模塊基本用不到。因?yàn)閷τ谌魏我粋€(gè)成熟的模塊而言,都具備了報(bào)文解析功能。像 requests, httpx, aiohttp 等等,以及一些 web 框架,它們在拿到報(bào)文之后會(huì)自動(dòng)解析成某個(gè)對象,我們直接通過指定的屬性獲取即可。
而 httptools 便是 uvicorn 的報(bào)文解析器,我們在使用 uvicorn 的時(shí)候,uvicorn 內(nèi)部也會(huì)自動(dòng)通過 httptools 將報(bào)文解析好,而不需要我們手動(dòng)解析。
因此這里介紹的 httptools 了解一下即可,我們只需要知道它是基于 C 實(shí)現(xiàn)的,性能非常高就行。但我們不會(huì)手動(dòng)使用它,而是在使用某個(gè)框架(uvicorn)的時(shí)候,由框架自動(dòng)幫我們將報(bào)文解析好。
到此這篇關(guān)于詳解Python中httptools模塊的使用的文章就介紹到這了,更多相關(guān)Python httptools模塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?Flask框架實(shí)現(xiàn)小紅書圖片無水印解析下載
這篇文章主要為大家介紹了Python?Flask框架實(shí)現(xiàn)小紅書圖片無水印解析下載,需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11centos6.8安裝python3.7無法import _ssl的解決方法
這篇文章主要介紹了centos6.8安裝python3.7無法import _ssl的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09Python3中簡單的文件操作及兩個(gè)簡單小實(shí)例分享
文件操作是我們?nèi)粘T谑褂胮ython的時(shí)候經(jīng)常會(huì)用到的,下面這篇文章主要給大家介紹了關(guān)于Python3中簡單的文件操作及兩個(gè)簡單小實(shí)例的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-06-06淺析python 定時(shí)拆分備份 nginx 日志的方法
本文給大家分享python 定時(shí)拆分備份 nginx 日志的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-04-04python之broadcast和numpy.sum()函數(shù)用法及說明
這篇文章主要介紹了python之broadcast和numpy.sum()函數(shù)用法及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06Python OpenCV高斯金字塔與拉普拉斯金字塔的實(shí)現(xiàn)
這篇文章主要介紹了Python OpenCV高斯金字塔與拉普拉斯金字塔的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03基于Python實(shí)現(xiàn)通過微信搜索功能查看誰把你刪除了
這篇文章主要介紹了基于Python實(shí)現(xiàn)微信搜索查看誰把你刪除了的相關(guān)資料,需要的朋友可以參考下2016-01-01