文档:https://zttp.marcelotryle.com
源码:https://github.com/Kludex/zttp
警告: zttp 目前处于实验阶段。API 和行为可能会随时更改,尚不适合用于生产环境。
zttp 是一个无 I/O (sans-IO) 的 HTTP 解析器,其引擎使用 Zig 编写。它支持 HTTP/1.1、HTTP/2 和 HTTP/3,并且不执行任何自身的 I/O 操作:你向它输入字节并提取事件,同时向它索取要发送的字节。它从不触碰套接字(socket),因此它可以与你喜欢的任何 I/O 方案配合使用。
它的理念与 h11 相同,但底层是用手写的 Zig 引擎代替了纯 Python。
核心特点是:
- 无 I/O (Sans-IO): 干净的、基于事件的 API。通过
receive_data 输入字节,通过 next_event 提取 Request / Data / EndOfMessage 事件。没有回调,没有套接字,没有意外。
- HTTP/1.1、HTTP/2 和 HTTP/3: 三者使用相同的事件 API,只需通过一个
protocol= 参数进行选择。
- 速度快: 在 14 个基准测试工作负载中的 13 个上,比
httptools(一个 C 语言解析器)还要快,并且比纯 Python 替代方案快大约 15 倍。
- 安全: 默认严格。它能够防御请求走私(request smuggling),拒绝纯 LF 换行符,限制每个缓冲区的大小,并且使用 Zig 开启了安全检查的编译模式发布。
- 类型化: 带有完整类型提示的
py.typed 包。
- 无依赖: wheel 安装包仅包含编译好的引擎,别无其他。
要求
zttp 需要 CPython 3.10+,并运行在 Linux、macOS 和 Windows 上。
安装
安装包中已经预先编译好了 Zig 核心,因此无需构建,也无需其他配置。有关从源码构建的信息,请参阅安装文档。
示例
你来扮演服务器:字节输入,事件输出。
import zttp
conn = zttp.Connection(zttp.SERVER)
conn.receive_data(b"GET /path?q=1 HTTP/1.1\r\nHost: example.com\r\n\r\n")
conn.next_event() # Request(method=b'GET', target=b'/path?q=1', http_version=b'1.1', headers=[(b'Host', b'example.com')])
conn.next_event() # EndOfMessage(trailers=[])
conn.next_event() # NEED_DATA
# 构建响应:
conn.send_response(200, [(b"Content-Length", b"5")])
conn.send_data(b"hello")
conn.end_message()
conn.data_to_send() # b'HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello'
读取端会产生 Request / Response / Data / EndOfMessage,或者在需要更多字节时产生 NEED_DATA 哨兵值。写入端则会序列化头部、正文数据和消息结尾,并为你处理好消息体分帧(Content-Length 或 chunked)。
无论你向 receive_data 传入什么——完整消息、片段还是单字节——zttp 都会进行缓冲并恢复。这里没有回调:你可以在准备好时主动拉取事件。这就是“无 I/O (sans-IO)”的含义。
一个 API,三个协议
protocol= 参数用于选择线路格式,而事件 API 保持不变:
import zttp
h1 = zttp.Connection(zttp.SERVER) # HTTP/1.1
h2 = zttp.Connection(zttp.SERVER, protocol=zttp.HTTP2) # HTTP/2
h3 = zttp.Connection(zttp.SERVER, protocol=zttp.HTTP3) # HTTP/3
在 HTTP/2 中,单个连接可以多路复用多个请求,因此 Request / Response / Data / EndOfMessage 事件会携带一个 stream_id,并且你是在一个 Stream 句柄上进行发送。出站流量控制已为你处理好:send_data 会根据对端窗口允许的大小发送,其余部分则会暂存,直到收到信用额度(credit)。
stream = h2.stream(request.stream_id)
stream.send_response(200, [(b"content-type", b"text/plain")])
stream.send_data(b"Hello, HTTP/2!")
stream.end_message()
h2.data_to_send() # 要放到网线上的 HTTP/2 帧
在 HTTP/3 中,传输层是 UDP,因此你通过 receive_datagram 输入完整的数据报(datagram),并拉取相同的事件。底层的 QUIC 传输(包保护、丢包恢复、拥塞控制、流重组)完全由 Zig 核心从头编写。HTTP/3 使用与 HTTP/2 相同的、基于流作用域的发送表面。
h3.receive_datagram(datagram)
h3.next_event() # 带有 stream_id 标记的相同的 Request / Data / EndOfMessage
有关完整的故事(包括每个协议更改的内容及原因),请参阅 HTTP/2 和 HTTP/3。HTTP/3 支持目前处于实验阶段:便利的服务器构造函数为本地使用创建了短暂的 TLS 身份,而非生产环境的服务器身份。
性能
与 httptools(Uvicorn 使用的 C 语言解析器)在相同请求下进行对比,两者均已验证能够提取相同的数据:
| 工作负载 |
zttp |
httptools |
zttp 对比 httptools |
| 简单 GET |
~1.24M 请求/秒 |
~1.07M 请求/秒 |
~1.16x |
| POST + JSON 请求体 |
~1.42M 请求/秒 |
~1.25M 请求/秒 |
~1.14x |
zttp 在基准测试套件的 14 个工作负载中的 13 个上击败了 httptools,同时保持了无 I/O (sans-IO) 和基于事件的特性,并且比纯 Python 替代方案快大约 15 倍。你可以通过运行 ./scripts/bench 来亲自验证。
这些是解析器的微基准测试,个位数的百分比优势接近运行时的噪声——有关完整的 14 个工作负载表格、方法论和注意事项,请参阅性能文档。
为什么它这么快
- Zig 核心中采用了 SWAR(SIMD Within A Register)换行符扫描器和编译时构建的字符类表,因此热循环是分支极少的数组查找。
- 消息体作为对解析缓冲区进行切片的单一
Data 事件发出,而不是像 httptools 那样在每次回调时进行复制。
- 头部列表直接在 Zig 中构建为
list[tuple[bytes, bytes]],没有每个头部触发的 Python 回调。
正确性与安全性
核心严格遵守 RFC 9112 第 6 节关于防范请求走私的分帧规则:处理 Content-Length 与 Transfer-Encoding 冲突、重复的 Content-Length 检查,并将多个 Transfer-Encoding 字段行合并为一个有序列表,以确保 chunked(分块传输)必须是唯一且最终的编码。默认情况下,行尾必须严格为 CRLF(拒绝纯 LF),块大小(chunk-size)必须严格为 1*HEXDIG,并且拒绝过时的行折叠(obsolate line folding)。
头部块、尾部(trailers)和接收缓冲区都受到保守的内置限制保护,因此恶意对端无法耗尽内存,出站序列化器会拒绝 CR/LF/控制字节以防止响应拆分。构建默认启用 Zig 的安全检查 ReleaseSafe 模式。格式错误的输入会引发 RemoteProtocolError;滥用发送 API 会引发 LocalProtocolError。
HTTP/2 层采用了相同的姿态:每个流的状态机强制执行 RFC 9113 的流生命周期,并具备 RFC 要求的精确的“流错误”与“连接错误”分类。
该解析器已经过两次对抗性安全审计(一次代码 review,以及针对 Node、Go、Python、Rust 和 C 服务器中真实 HTTP 解析器 CVE 的 CVE 导向 review);zig build fuzz 会对核心进行对抗性输入的模糊测试(fuzzing)。有关 zttp 防御的内容、强制执行的具体限制以及集成商的责任,请参阅 THREAT_MODEL.md。
许可证
本项目采用 BSD-3-Clause 许可证的条款授权。
加入我们
Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:
- 供稿,分享自己使用 Zig 的心得
- 改进 ZigCC 组织下的开源项目
- 加入微信群、Telegram 群组、Google Groups 与更多 Zig 爱好者交流
文档:https://zttp.marcelotryle.com
源码:https://github.com/Kludex/zttp
警告: zttp 目前处于实验阶段。API 和行为可能会随时更改,尚不适合用于生产环境。
zttp 是一个无 I/O (sans-IO) 的 HTTP 解析器,其引擎使用 Zig 编写。它支持 HTTP/1.1、HTTP/2 和 HTTP/3,并且不执行任何自身的 I/O 操作:你向它输入字节并提取事件,同时向它索取要发送的字节。它从不触碰套接字(socket),因此它可以与你喜欢的任何 I/O 方案配合使用。
它的理念与
h11相同,但底层是用手写的 Zig 引擎代替了纯 Python。核心特点是:
receive_data输入字节,通过next_event提取Request/Data/EndOfMessage事件。没有回调,没有套接字,没有意外。protocol=参数进行选择。httptools(一个 C 语言解析器)还要快,并且比纯 Python 替代方案快大约 15 倍。py.typed包。要求
zttp 需要 CPython 3.10+,并运行在 Linux、macOS 和 Windows 上。
安装
安装包中已经预先编译好了 Zig 核心,因此无需构建,也无需其他配置。有关从源码构建的信息,请参阅安装文档。
示例
你来扮演服务器:字节输入,事件输出。
读取端会产生
Request/Response/Data/EndOfMessage,或者在需要更多字节时产生NEED_DATA哨兵值。写入端则会序列化头部、正文数据和消息结尾,并为你处理好消息体分帧(Content-Length 或 chunked)。无论你向
receive_data传入什么——完整消息、片段还是单字节——zttp 都会进行缓冲并恢复。这里没有回调:你可以在准备好时主动拉取事件。这就是“无 I/O (sans-IO)”的含义。一个 API,三个协议
protocol=参数用于选择线路格式,而事件 API 保持不变:在 HTTP/2 中,单个连接可以多路复用多个请求,因此
Request/Response/Data/EndOfMessage事件会携带一个stream_id,并且你是在一个Stream句柄上进行发送。出站流量控制已为你处理好:send_data会根据对端窗口允许的大小发送,其余部分则会暂存,直到收到信用额度(credit)。在 HTTP/3 中,传输层是 UDP,因此你通过
receive_datagram输入完整的数据报(datagram),并拉取相同的事件。底层的 QUIC 传输(包保护、丢包恢复、拥塞控制、流重组)完全由 Zig 核心从头编写。HTTP/3 使用与 HTTP/2 相同的、基于流作用域的发送表面。有关完整的故事(包括每个协议更改的内容及原因),请参阅 HTTP/2 和 HTTP/3。HTTP/3 支持目前处于实验阶段:便利的服务器构造函数为本地使用创建了短暂的 TLS 身份,而非生产环境的服务器身份。
性能
与
httptools(Uvicorn 使用的 C 语言解析器)在相同请求下进行对比,两者均已验证能够提取相同的数据:zttp 在基准测试套件的 14 个工作负载中的 13 个上击败了
httptools,同时保持了无 I/O (sans-IO) 和基于事件的特性,并且比纯 Python 替代方案快大约 15 倍。你可以通过运行./scripts/bench来亲自验证。这些是解析器的微基准测试,个位数的百分比优势接近运行时的噪声——有关完整的 14 个工作负载表格、方法论和注意事项,请参阅性能文档。
为什么它这么快
Data事件发出,而不是像httptools那样在每次回调时进行复制。list[tuple[bytes, bytes]],没有每个头部触发的 Python 回调。正确性与安全性
核心严格遵守 RFC 9112 第 6 节关于防范请求走私的分帧规则:处理 Content-Length 与 Transfer-Encoding 冲突、重复的 Content-Length 检查,并将多个 Transfer-Encoding 字段行合并为一个有序列表,以确保 chunked(分块传输)必须是唯一且最终的编码。默认情况下,行尾必须严格为 CRLF(拒绝纯 LF),块大小(chunk-size)必须严格为
1*HEXDIG,并且拒绝过时的行折叠(obsolate line folding)。头部块、尾部(trailers)和接收缓冲区都受到保守的内置限制保护,因此恶意对端无法耗尽内存,出站序列化器会拒绝 CR/LF/控制字节以防止响应拆分。构建默认启用 Zig 的安全检查
ReleaseSafe模式。格式错误的输入会引发RemoteProtocolError;滥用发送 API 会引发LocalProtocolError。HTTP/2 层采用了相同的姿态:每个流的状态机强制执行 RFC 9113 的流生命周期,并具备 RFC 要求的精确的“流错误”与“连接错误”分类。
该解析器已经过两次对抗性安全审计(一次代码 review,以及针对 Node、Go、Python、Rust 和 C 服务器中真实 HTTP 解析器 CVE 的 CVE 导向 review);
zig build fuzz会对核心进行对抗性输入的模糊测试(fuzzing)。有关 zttp 防御的内容、强制执行的具体限制以及集成商的责任,请参阅THREAT_MODEL.md。许可证
本项目采用 BSD-3-Clause 许可证的条款授权。
加入我们
Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: