详解 httptools 模块,一个 HTTP 解析器

简介: 详解 httptools 模块,一个 HTTP 解析器

如果你用过 FastAPI 的话,那么你一定知道 uvicorn,它是一个基于 uvloop 和 httptools 实现的高性能 ASGI 服务器。

其中 uvloop 采用 Cython 编写,用于替换 asyncio 中的事件循环,可以让 asyncio 速度增加 2 到 4 倍。而 httptools 是基于 C 语言实现的 HTTP 解析器,用来解析 HTTP 请求的。

本次就来聊一聊 httptools 这个模块的详细用法,至于 uvloop、uvicorn 等相关内容,后续我会一点一点补充上去,并从源码的角度全给说明白(挖了个坑)。

httptools 是一个 HTTP 解析器,它首先提供了一个 parse_url 函数,用来解析 URL。

import httptools
# 第一个参数必须是 bytes 对象
url = httptools.parse_url(
    b"http://www.baidu.com"
)
# 返回一个 URL 对象
print(url.__class__)
"""
<class 'httptools.parser.parser.URL'>
"""

那么这个 URL 对象有哪些属性呢?

3b557254fcaf785cb215b169927de26d.png

通过源码可知,总共有七个属性,我们来测试一下。

import httptools
# 第一个参数是 bytes 对象
url = b"http://satori:123456@www.baidu.com:80/s?wd=koishi#flag"
url_obj = httptools.parse_url(url)
print("协议:", url_obj.schema)
print("IP:", url_obj.host)
print("端口:", url_obj.port)
print("路径:", url_obj.path)
print("查询参数:", url_obj.query)
print("锚点:", url_obj.fragment)
print("用户信息:", url_obj.userinfo)
"""
协议: b'http'
IP: b'www.baidu.com'
端口: 80
路径: b'/s'
查询参数: b'wd=koishi'
锚点: b'flag'
用户信息: b'satori:123456'
"""

比较简单,如果参数不符合 URL 的标准格式,那么会抛出 HttpParserInvalidURLError 错误。

然后是 HTTP 请求报文和响应报文的解析,因为报文只是一坨字节流,需要将它解析成某个 Request 对象或 Response 对象,而 httptools 就是干这件事情的。

首先来看一下报文格式,请求报文如下:

2fa5814cabf254d4bb0fb7fae58fbd2a.png

接下来是响应报文:

e94a43346ed39346677b28196d3e30c4.png

所以无论是请求报文还是响应报文,都由 起始行 + 请求头/响应头 + 请求体/响应体 组成。而我们在拿到原始的报文之后,也可以很方便地进行解析,从图中可以看出最后一个 Header 字段和响应体之间有两个换行,而换行用 \r\n 表示。因此我们只要按照 "\r\n\r\n" 进行 split 即可,会得到一个数组,数组的第二个元素就是请求体/响应体,第一个元素就是起始行 + 请求头/响应头

然后对数组的第一个元素按照 "\r\n" 再进行 split,又可以得到一个数组,该数组的第一个元素就是起始行,剩余的元素就是请求头/响应头

所以我们在拿到报文之后,完全可以自己手动解析,但 httptools 是用 C 实现的,所以速度会快一些,但干的事情是一样的。下面来看看 httptools 如何解析请求报

from pprint import pprint
import httptools
# 请求报文
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:
    """
    将请求报文的解析结果封装成 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
# 实例化 Request 对象
request = Request()
# 将 request 作为参数传到 HttpRequestParser 中
parser = httptools.HttpRequestParser(request)
# 传入请求报文,进行解析
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'
"""
# 以上几个都是 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 也是请求头的一部分,但在解析的时候会单独拿出来
# 再解析成一个字典,然后通过 request.cookies 获取
# 获取请求体
print(request.body)
"""
b'{"name":"satori","age":17}'
"""

以上就是请求报文的解析,再来看看响应报文。

from pprint import pprint
import httptools
# 响应报文
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:
    """
    将响应报文的解析结果封装成 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
# 实例化 Response 对象
response = Response()
# 将 response 作为参数传到 HttpResponseParser 中
parser = httptools.HttpResponseParser(response)
# 传入响应报文,进行解析
parser.feed_data(response_payload)
# 获取 HTTP 版本
print(parser.get_http_version())
"""
1.1
"""
# 是否是长链接(不指定 Connection,默认为长连接)
print(parser.should_keep_alive())
"""
True
"""
# 获取状态码
print(parser.get_status_code())
"""
b'OK'
"""
# 获取状态码对应的描述
print(response.status)
"""
b'OK'
"""
# 获取响应头
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'}
"""
# 获取响应体
print(response.body)
"""
b'name: satori, age: 17'
"""

以上就是请求报文和响应报文的解析,但如果你不是手动发送 TCP 请求的话,那么该模块基本用不到。因为对于任何一个成熟的模块而言,都具备了报文解析功能。像 requests, httpx, aiohttp 等等,以及一些 web 框架,它们在拿到报文之后会自动解析成某个对象,我们直接通过指定的属性获取即可。

而 httptools 便是 uvicorn 的报文解析器,我们在使用 uvicorn 的时候,uvicorn 内部也会自动通过 httptools 将报文解析好,而不需要我们手动解析。

因此这里介绍的 httptools 了解一下即可,我们只需要知道它是基于 C 实现的,性能非常高就行。但我们不会手动使用它,而是在使用某个框架(uvicorn)的时候,由框架自动帮我们将报文解析好。

相关文章
|
3月前
|
缓存 监控 搜索推荐
301重定向实现原理全面解析:从HTTP协议到SEO最佳实践
301重定向是HTTP协议中的永久重定向状态码,用于告知客户端请求的资源已永久移至新URL。它在SEO中具有重要作用,能传递页面权重、更新索引并提升用户体验。本文详解其工作原理、服务器配置方法(如Apache、Nginx)、对搜索引擎的影响及最佳实践,帮助实现网站平稳迁移与优化。
448 68
|
2月前
|
缓存 网络协议 UED
深度解析HTTP协议从版本0.9至3.0的演进和特性。
总的来说,HTTP的演进是互联网技术不断发展和需求日益增长的结果。每一次重要更新都旨在优化性能,增进用户体验,适应新的应用场景,而且保证了向后兼容,让互联网的基础架构得以稳定发展。随着网络技术继续进步,我们可以预期HTTP协议在未来还会继续演化。
341 0
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
4月前
|
网络协议 API Python
解析http.client与requests在Python中的性能比较和改进策略。
最后,需要明确的是,这两种库各有其优点和适用场景。`http.client` 更适合于基础且并行的请求,`requests` 则因其易用且强大的功能,更适用于复杂的 HTTP 场景。对于哪种更适合你的应用,可能需要你自己进行实际的测试来确定。
121 10
|
5月前
|
域名解析 网络协议 网络安全
SSL证书验证全攻略:DNS/HTTP/手动解析怎么选?
SSL证书在网络安全中至关重要,1Panel提供三种验证方式:DNS验证、HTTP验证和手动解析。DNS验证便捷,适合CDN网站;HTTP验证快速,需服务器在线;手动解析灵活,但操作复杂。根据需求选择合适确认方式,定期检查证书状态。
622 2
|
5月前
|
JavaScript 前端开发 开发者
Node学习笔记:HTTP模块
总的来说,Node.js的HTTP模块是一个强大的工具,可以帮助你处理HTTP协议的各种需求。无论你是想开设自己的餐厅(创建服务器),还是想去别的餐厅点菜(发出请求),HTTP模块都能满足你的需求。
132 18
|
5月前
|
安全 网络协议 算法
HTTP/HTTPS与SOCKS5协议在隧道代理中的兼容性设计解析
本文系统探讨了构建企业级双协议隧道代理系统的挑战与实现。首先对比HTTP/HTTPS和SOCKS5协议特性,分析其在工作模型、连接管理和加密方式上的差异。接着提出兼容性架构设计,包括双协议接入层与统一隧道内核,通过协议识别模块和分层设计实现高效转换。关键技术部分深入解析协议转换引擎、连接管理策略及加密传输方案,并从性能优化、安全增强到典型应用场景全面展开。最后指出未来发展趋势将更高效、安全与智能。
187 1
|
6月前
|
安全 网络安全 数据安全/隐私保护
HTTP 与 HTTPS 协议及 SSL 证书解析-http和https到底有什么区别?-优雅草卓伊凡
HTTP 与 HTTPS 协议及 SSL 证书解析-http和https到底有什么区别?-优雅草卓伊凡
297 3
|
6月前
|
网络安全
网络问题解析:如何解决CondaHTTPError HTTP 000 CONNECTION FAILED错误。
以上就是斯诺普为你准备的解决Conda出现HTTP连接错误的手术室。希望这辆小车可以顺利驶出棘手的泥潭,再次在自由的大路上疾驰。一切的尝试和努力,只为更好的探索与开发。
218 17
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
648 29

热门文章

最新文章

推荐镜像

更多
  • DNS