詳解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 請(qǐng)求的。
本次就來聊一聊 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?對(duì)象 url?=?httptools.parse_url( ????b"http://www.baidu.com" ) #?返回一個(gè)?URL?對(duì)象 print(url.__class__) """ <class?'httptools.parser.parser.URL'> """
那么這個(gè) URL 對(duì)象有哪些屬性呢?

通過源碼可知,總共有七個(gè)屬性,我們來測試一下。
import?httptools
#?第一個(gè)參數(shù)是?bytes?對(duì)象
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 請(qǐng)求報(bào)文和響應(yīng)報(bào)文的解析,因?yàn)閳?bào)文只是一坨字節(jié)流,需要將它解析成某個(gè) Request 對(duì)象或 Response 對(duì)象,而 httptools 就是干這件事情的。
首先來看一下報(bào)文格式,請(qǐng)求報(bào)文如下:

接下來是響應(yīng)報(bào)文:

所以無論是請(qǐng)求報(bào)文還是響應(yīng)報(bào)文,都由 起始行 + 請(qǐng)求頭/響應(yīng)頭 + 請(qǐng)求體/響應(yīng)體 組成。而我們?cè)谀玫皆嫉膱?bào)文之后,也可以很方便地進(jìn)行解析,從圖中可以看出最后一個(gè) Header 字段和響應(yīng)體之間有兩個(gè)換行,而換行用 \r\n 表示。因此我們只要按照 "\r\n\r\n" 進(jìn)行 split 即可,會(huì)得到一個(gè)數(shù)組,數(shù)組的第二個(gè)元素就是請(qǐng)求體/響應(yīng)體,第一個(gè)元素就是起始行 + 請(qǐng)求頭/響應(yīng)頭。
然后對(duì)數(shù)組的第一個(gè)元素按照 "\r\n" 再進(jìn)行 split,又可以得到一個(gè)數(shù)組,該數(shù)組的第一個(gè)元素就是起始行,剩余的元素就是請(qǐng)求頭/響應(yīng)頭。
所以我們?cè)谀玫綀?bào)文之后,完全可以自己手動(dòng)解析,但 httptools 是用 C 實(shí)現(xiàn)的,所以速度會(huì)快一些,但干的事情是一樣的。下面來看看 httptools 如何解析請(qǐng)求報(bào)文:
from?pprint?import?pprint
import?httptools
#?請(qǐng)求報(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:
????"""
????將請(qǐng)求報(bào)文的解析結(jié)果封裝成?Request?對(duì)象
????"""
????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?對(duì)象
request?=?Request()
#?將?request?作為參數(shù)傳到?HttpRequestParser?中
parser?=?httptools.HttpRequestParser(request)
#?傳入請(qǐng)求報(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
"""
#?獲取請(qǐng)求方法
print(parser.get_method())
"""
b'POST'
"""
#?以上幾個(gè)都是?HttpRequestParser?對(duì)象的方法
#?獲取路徑
print(request.path)
"""
b'/index?a=1'
"""
#?獲取請(qǐng)求頭
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?也是請(qǐng)求頭的一部分,但在解析的時(shí)候會(huì)單獨(dú)拿出來
#?再解析成一個(gè)字典,然后通過?request.cookies?獲取
#?獲取請(qǐng)求體
print(request.body)
"""
b'{"name":"satori","age":17}'
"""以上就是請(qǐng)求報(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?對(duì)象
????"""
????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?對(duì)象
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)碼對(duì)應(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'
"""以上就是請(qǐng)求報(bào)文和響應(yīng)報(bào)文的解析,但如果你不是手動(dòng)發(fā)送 TCP 請(qǐng)求的話,那么該模塊基本用不到。因?yàn)閷?duì)于任何一個(gè)成熟的模塊而言,都具備了報(bào)文解析功能。像 requests, httpx, aiohttp 等等,以及一些 web 框架,它們?cè)谀玫綀?bào)文之后會(huì)自動(dòng)解析成某個(gè)對(duì)象,我們直接通過指定的屬性獲取即可。
而 httptools 便是 uvicorn 的報(bào)文解析器,我們?cè)谑褂?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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
安裝python時(shí)MySQLdb報(bào)錯(cuò)的問題描述及解決方法
這篇文章主要介紹了安裝python時(shí)MySQLdb報(bào)錯(cuò)的問題描述及解決方法,需要的朋友可以參考下2018-03-03
Python判斷兩個(gè)文件是否相同與兩個(gè)文本進(jìn)行相同項(xiàng)篩選的方法
今天小編就為大家分享一篇關(guān)于Python判斷兩個(gè)文件是否相同與兩個(gè)文本進(jìn)行相同項(xiàng)篩選的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
關(guān)于Python中進(jìn)度條的六個(gè)實(shí)用技巧分享
在項(xiàng)目開發(fā)過程中加載、啟動(dòng)、下載項(xiàng)目難免會(huì)用到進(jìn)度條,下面這篇文章主要給大家介紹了關(guān)于Python中進(jìn)度條的六個(gè)實(shí)用技巧,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
PyQt5 QTableView設(shè)置某一列不可編輯的方法
今天小編就為大家分享一篇PyQt5 QTableView設(shè)置某一列不可編輯的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-06-06
python給指定csv表格中的聯(lián)系人群發(fā)郵件(帶附件的郵件)
這篇文章主要介紹了python給指定csv表格中的聯(lián)系人群發(fā)郵件,本文通過代碼講解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
使用python實(shí)現(xiàn)畫AR模型時(shí)序圖
今天小編就為大家分享一篇使用python實(shí)現(xiàn)畫AR模型時(shí)序圖,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11
Python實(shí)現(xiàn)從訂閱源下載圖片的方法
這篇文章主要介紹了Python實(shí)現(xiàn)從訂閱源下載圖片的方法,涉及Python采集的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03

