pingora 源码解析
pingora 是 Cloudflare 开源的 Rust 框架,用来写可编程的 HTTP 代理。它跑在 tokio 上,但不用 hyper:HTTP/1 是自己在 httparse 之上实现的,HTTP/2 用 h2,TLS 有 openssl、boringssl、rustls、s2n 四个后端可选。写一个代理,就是实现 ProxyHttp 这个 trait:必须写的只有 new_ctx 和 upstream_peer(请求发往哪里),其余四十个方法都有默认实现(其中两个要开 upstream_modules feature 才有),在请求的固定阶段被调用。仓库 README 的说法是「a Rust framework to build fast, reliable and programmable networked systems」。这句话落在哪些代码上,和 nginx 那样的配置驱动代理、用 hyper 自己搭的代理有什么不同,见 pingora 解决什么问题。
锁定的版本
| 项 | 本卷 | 说明 |
|---|---|---|
| 版本 | 0.9.0 | 工作区 22 个 crate 同一个版本号,一起发版 |
| tag | 0.9.0 → 702f690(2026-09-09,「Update changelog for 0.9.0」) | crates.io 上各包 .cargo_vcs_info.json 记的就是这个 commit;CHANGELOG 给 0.9.0 写的日期是 2026-09-04 |
| 历史 | 首个提交 2023-05-08,到 tag 共 764 个提交;0.1.0 在 CHANGELOG 里记为 2024-02-28(tag 指向的 b4264f1 是 2024-04-05) | 仓库从 Cloudflare 内部仓库同步,大多数提交不带 PR 号 |
| tag 之后 | main 到 84f21ee(2026-10-22)又有 36 个提交,没有发版 | 本卷不跟;某页讲的代码在这之后改过,会提一句 |
| tokio | 实验一律钉在 1.53.2 | tokio 1.54 让 pingora 0.9.0 接受第一个连接就 panic,原因和修复见怎么读这一卷 |
源码链接一律指向 tag,形如 HttpProxy::process_request。
讲什么
pingora-core:Server与服务、监听与 L4 流、HTTP/1 服务端和客户端、HTTP/2、TLS 抽象、上游连接器与HttpPeer、HTTP 模块。pingora-proxy:ProxyHttp的阶段与钩子、HttpProxy::process_request、h1 / h2 双向转发、错误与重试、上游模块。pingora-cache:缓存状态机、缓存键与元数据、缓存锁、存储与淘汰。- 负载均衡与限流:
pingora-load-balancing、pingora-ketama、pingora-limits。 - 基础组件:
pingora-pool、pingora-timeout、pingora-memory-cache与TinyUFO、pingora-lru、pingora-http、pingora-error、pingora-runtime、pingora-header-serde。 - 不讲:boringssl 和 s2n 后端(openssl 后端讲到的地方对照一句)、
pingora-prometheus、pingora-foundations、sentry 与 dial9 集成、Windows 支持。
三条主线
全卷沿三条主线展开,每页开头标出自己在哪条线上:
- 请求主线(一次
curl http://127.0.0.1:<端口>/):监听器 accept,TransportStack建Stream、set_buffer→HttpServerApp::process_new_http,HTTP/1 服务端读请求头 →HttpProxy::process_request依次调early_request_filter、request_filter、upstream_peer→ 连接器从池里取上游连接或新建 →upstream_request_filter→ h1 双向转发请求体和响应体,中间经过upstream_response_filter、response_filter、response_body_filter→logging→ 下游连接回到 keep-alive 循环,上游连接回池。 - 服务主线(
Server的一生):Server::new读选项和配置 →bootstrap(升级时从旧进程接收监听 fd)→add_service→run_forever:每个服务一个运行时、起监听 → 等信号:SIGQUIT平滑升级、SIGTERM平滑停机、SIGINT立刻退出 → 停机流程。 - 缓存主线(同一个 URL 请求两次):
request_cache_filter打开缓存 →cache_key_callback→ 查找,miss → 缓存锁 → 上游响应 →response_cache_filter判断能否缓存 → 写入存储 → 第二次请求 hit,cache_hit_filter,不连上游。
每条线的每一步落在哪个函数、哪一页讲,见怎么读这一卷。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> server[02 服务器] overview --> proto[03 协议] proto --> conn[04 连接上游] conn --> proxy[05 代理] server --> proxy proxy --> cache[06 缓存] proxy --> lb["07 负载均衡与限流"] cache --> comp[08 基础组件] lb --> comp comp --> design[09 设计取舍] design --> pit[10 常见坑] pit --> site[11 在本站中] site --> hands[12 动手]
实验环境
关于行为的说法(钩子按什么顺序调、上游连接有没有复用、SIGTERM 之后先停什么、报什么错)都配一个小程序和它的真实输出。平台是 M2 Max、macOS 27、rustc 1.99.0,tokio 1.53.2。没有 Linux 机器可用,而 pingora 的 README 写明「Linux is our tier 1 environment」:平滑升级传 fd、一部分 socket 选项只在 #[cfg(target_os = "linux")] 下编译,这些地方只读源码,页面上写明。具体的模板、feature 和端口约定见怎么读这一卷。
各部分
- 00 导读:锁定版本;三条主线每一步落在哪;实验环境与 tokio 1.53.2 的原因;feature 表与它们之间的约束;crate 依赖图、各 crate 入口和规模。
- 01 全景:pingora 在代理的几种写法里的位置;一次请求经过的全部钩子(实测顺序和上游端口);一个
Server从启动到SIGTERM退出(实测线程名和停机顺序)。 - 02 服务器:配置与命令行、服务与监听器、运行时、平滑升级与停机。
- 03 协议:L4 流、HTTP/1 服务端、HTTP/1 的 body、HTTP/2、TLS、HTTP 模块与压缩。
- 04 连接上游:
HttpPeer、L4 连接、连接池、HTTP 连接器。 - 05 代理:
ProxyHttp、process_request、双向转发、错误与重试、上游模块。 - 06 缓存:缓存状态机、键与元数据、缓存锁、存储与淘汰。
- 07 负载均衡与限流:
LoadBalancer、选择算法、健康检查、限流。 - 08 基础组件:TinyUFO 与内存缓存、超时、请求头与错误。
- 09 设计取舍:自己写 HTTP/1、固定阶段的钩子、每服务一个运行时、跨连接的连接池、四选一的 TLS 后端,和 hyper、tower、tokio 对照。
- 10 常见坑:报错原文做标题,每个都先跑出最小复现。
- 11 在本站中:本站网关(pingora 0.8.0)的每一处调到 pingora 的哪段源码,升级到 0.9.0 会碰到什么。
- 12 动手:不依赖 pingora,约 500 行写一个带钩子和上游连接池的 mini 代理。
适合谁读
- 用过 nginx、想知道「用代码写代理」是什么样子的读者:从导读开始,再读 01 全景 的三页,然后是 05 代理。
- 读过 hyper 卷、想看同样在 tokio 上的另一套 HTTP 实现的读者:先读一次请求,再读 03 协议 和 04 连接上游,最后读 09 设计取舍。
- 读过 tower 卷 的读者:tower 的中间件是
Service套Service,pingora 是一个 trait 上的固定阶段,对照从 ProxyHttp 开始。 - 需要的前置知识:async Rust 与 tokio 的运行时、
async_trait;HTTP/1.1 的 keep-alive、Content-Length与 chunked;知道反向代理里「下游」(downstream,客户端一侧)和「上游」(upstream,后端服务一侧)指什么。