Rust in Everything
基于 pingora 0.9.0

协议

这一部分讲 pingora-core 里「字节进来、HTTP 出去」的那一层:src/protocols/(L4 流、HTTP/1、HTTP/2、TLS 和它们共用的摘要类型)和 src/modules/(HTTP 模块)。监听器怎么 accept 是服务器部分的事,连接上游时同一套类型怎么用是连接上游部分的事,钩子按什么顺序调用是代理部分的事。

pingora 不用 hyper。HTTP/1 是它自己在 httparse 之上写的,请求头和消息体各有一个状态机;HTTP/2 交给 h2 crate,pingora 只管握手参数、拆流和校验;TLS 有四个后端,用 cfg 拼成一套同名的 API。所有连接最后都装进同一个 Box<dyn IO>,HTTP 层不关心底下是 TCP、Unix 域套接字还是 TLS。

  1. L4 流:l4::Stream 是 RawStream 外面套 BufStream、回退栈和摘要;accept 时缓冲容量给 0、握手时换成 64 KiB,上游连接一直是 0;tokio 1.54 拒绝零容量 BufReader 的来龙去脉,以及 main 上 5b30d71 的修法。
  2. HTTP/1 服务端:read_request 用 httparse 在栈上解析、零拷贝建请求头并保留大小写;各种 400 的实测原因;0.9.0 的请求目标校验、默认删逐跳头和可选的 pipelining;第一个请求之后空闲连接不再超时。
  3. HTTP/1 消息体:BodyReader 和 BodyWriter 的状态机;22 种边界和畸形请求体的实测回应;扩展长度上限只在块头被拆开时生效;空数据写成 0\r\n\r\n 的问题。
  4. HTTP/2:ALPN 和 h2c 两条入口,h2c 前言检测会一直等满 24 字节;0.9.0 起默认 100 个并发流和 64 KiB 头列表,传 Some(H2Options::new()) 会把它们丢掉;每个流一个会话,1xx 被丢弃。
  5. TLS:四个后端怎么用 cfg 拼出同一套名字;TlsSettings::intermediate 在 openssl 和 rustls 上做的事完全不同;自签名证书在两个后端上各握手一遍,未知 ALPN 时一个继续、一个失败。
  6. HTTP 模块与压缩:HttpModule 是按 order 排好、每个请求新建一份的过滤器;ResponseCompression 按 Accept-Encoding 的第一项选算法、不看 q 值;在 request_filter 里才打开压缩为时已晚。
flowchart TB
  tcp["TcpStream"] --> l4["l4::Stream"]
  l4 --> tls["TLS 流"]
  l4 --> io["Box dyn IO"]
  tls --> io
  io --> v1["v1 HttpSession"]
  io --> v2["h2 握手"]
  v2 --> s2["每流一个会话"]
  v1 --> body["消息体"]
  v1 --> mods["HTTP 模块"]
  s2 --> mods

前三页按请求主线的顺序读:字节先进 L4 流,再被 HTTP/1 解析成请求头,然后是消息体。HTTP/2 和 TLS 是这条线上的两个岔路,模块那一页回到代理的钩子之间。和 hyper 的逐项对照放在设计取舍。

💬 关联讨论

发起讨论
加载中...