土法炼钢 · 系统与基础设施

【HAProxy 数据面】HTX 与 HTTP 路径:内部表示、改写落点与协议分叉

文章导航

分类入口
networkproxy
标签入口
#haproxy#htx#http#http2#rewrite#mux#reverse-proxy#3.4

目录

上一篇把 mux 与 stream 分层钉住。本篇进入协议表示轴:HTTP 在 HAProxy 内部以什么形态流动,改写落在哪,以及为何「先转成 H1 字节再处理」是一条已经否定的弯路。

本文不是 http-request set-header 菜谱——那是 network/56 的范围。这里只回答机制问题:规则引擎看见的是结构化消息还是原始套接字缓冲?H2 与 H1 在何处分叉、在何处汇合?相对 mode tcp,这条路径贵在哪里?

本文是「HAProxy / 数据面代理内核」系列第 5 篇(共 14 篇)。→ 系列目录

篇目 核心内容
第 4 篇 · Stream Mux 连接与流
第 5 篇 · HTX HTTP 内部表示与改写落点
第 6 篇 · ACL Rules 求值顺序与副作用
第 10 篇 · SSL TLS 与 HTTP 路径的正交轴

版本锚定:HAProxy 3.4.3;内部文档 doc/internals/api/htx-api.txt(HTX 动机与块模型);源码 tag v3.4.3src/htx.csrc/http_htx.csrc/mux_h1.csrc/mux_h2.c。不写未测的改写 QPS 对比。


一、HTX 是什么:版本无关的内部 HTTP 消息

内部 API 文档(htx-api.txt)把历史动机写得很直白:

  1. 早期:HTTP 消息以接近原始字节的方式躺在 buffer 里,解析状态另存;利于转发,不利于改写;且以 HTTP/1 为中心。
  2. H2 第一代弯路:把 H2 消息转成 H1 再走旧路径——两遍解析 + 中间转换,反向再转回去;甚至长期只能较好支持客户端侧 H2。
  3. HTX:用与线协议版本无关、自描述的内部表示替换旧模型;解析在 mux(h1/h2/…),处理在 stream/规则,二者分离。

工作定义:

flowchart LR
  H1["HTTP/1 wire"] --> M1["mux_h1"]
  H2["HTTP/2 wire"] --> M2["mux_h2"]
  M1 --> HTX["HTX blocks"]
  M2 --> HTX
  HTX --> Rules["ACL / http-request rules"]
  Rules --> HTX2["HTX possibly rewritten"]
  HTX2 --> MOut["egress mux_h1/h2/..."]
  MOut --> Wire2["Wire bytes"]

对排障:规则与改写看到的是 HTX 视图;「抓包看到的字节」是 mux 重新编码后的线格式。两者不一致时,先问改写是否发生,再问上游协议是否不同。

HTX 消息在缓冲中「看起来已满」的表象,来自其把结构塞进 buffer 的方式:中间层若仍按「普通字节缓冲还有空位」去推理,会误判流控。内部文档明确:从 HTX 视角,承载消息的 buffer 常表现为满——这是表示法后果,不是一定发生了应用层背压。读源码或调 tune 相关缓冲参数时,要把「HTX 容器满」与「TCP 窗口满」分开。


二、改写 / 重定向落点:HTX,而非「永远抠原始缓冲」

http-request / http-response 等动作(增删改首部、路径改写、redirect、reject…)在机制上针对已解析进 HTX 的消息(或基于其字段的分析结果)。这带来几条不变量:

不变量 含义
解析先于处理 畸形消息在 mux 阶段失败时,规则可能根本看不到「半包原文」
改写改的是内部块 随后由出口 mux 编码为 H1 或 H2 帧;不是在 socket 上 splice 一段历史字节再魔改
下游协议 ≠ 上游协议 同一 HTX 可被另一侧 mux 编码成不同线协议
L4 路径可跳过 mode tcp 不走这套 HTTP 分析;硬在 TCP 前端上套 HTTP 改写是配置错误,不是「HTX 丢了」

与「应用菜谱」的边界:本篇不解释某个 set-header 表达式怎么写;只强调——你在 Runtime/配置里改的 HTTP 语义,落点是 HTX 管道,成本也在这条管道上。

Body 与 trailer:分块 H1、H2 DATA/HEADERS 的 trailer 出现条件不同;mux 必须能处理「有无 trailer」两种世界(内部文档明示)。大 body 场景下,改写若触及 body,成本模型与「只改首部」完全不同——平台规范应默认限制 body 改写面。


三、HTTP/1 与 HTTP/2:在 mux 边界分叉,在 HTX 汇合

阶段 HTTP/1 HTTP/2
线格式 文本起始行 + 首部 + 可选分块 body 二进制帧、流 ID、HPACK、窗口
Mux 职责 扫描/状态机解析进 HTX;连接级 keep-alive / 空闲超时任务 帧复用、流状态、窗口;伪首部 → HTX 起始行
并发模型 同连接事务多串行(或受限 pipelining,代理侧通常谨慎) 同连接多 stream
汇合点 HTX 块序列 同左
出口 再编码为 H1 或交给另一 mux 再编码为 H2 或交给另一 mux

分叉的运维含义:

  1. 失败粒度:H2 可重置单流;H1 常表现为连接级关闭或串扰下一条事务。
  2. 头压缩与表状态:H2 的 HPACK 状态在连接上;改写头部集合会影响编码大小,但不改变「规则看见的是 HTX 字段」这一层。
  3. 观测:不要用「H1 时代的一条 access log = 一个 FD」硬套 H2;应按 stream/请求关联(第 13 篇收束字段)。

HTTP/3/QUIC 若在构建与配置中启用,属于另一条传输分叉(UDP、独立监听与故障转移经济学);本篇不展开成熟度争论——第 14 篇选型会把它列为开放问题,避免在无环境声明下写「已与 H2 完全同构」。


四、成本:相对纯 L4 路径贵在哪里

不做本机 benchmark,只给可检验的成本项清单(每一项都应对应源码路径或配置开关,而不是形容词):

成本项 L4 / mux_pt HTTP + HTX
解析 基本不解析应用 mux 必须解析到可建 HTX
规则引擎 内容切换手段有限(端口/IP/TCP 检查等) ACL 与 http 动作可深度读字段
改写 通常不改应用语义 首部/起始行改写 + 可能的 defrag
缓冲形态 字节缓冲转发;或条件允许的快速转发 HTX 块管理;文档描述的空洞与整理
协议转换 H2↔︎H1 等在出口重新编码
超时集合 连接/隧道类为主 另加 http-request / keep-alive 等

工程判断(标明为判断):需要按 Host/路径/Cookie 做切换或改写时,付 HTX 税是买能力;纯透传 TLS 或二进制协议时,强行 mode http 是买噪音。 反向判断也成立:为了「统一观测」把一切抬到 HTTP,可能把 L4 故障伪装成应用层 400——第 13 篇会要求先看模式。

与第 1 篇争论表对齐:「一切经 HTTP」vs「保留瘦 TCP」没有全局赢家;赢家是故障域是否与业务语义同层


五、与规则引擎、TLS 的衔接(预告)

相对 Envoy:Envoy 用 codec 事件喂 HTTP filter;HAProxy 用 HTX 喂规则与分析器。两边都把「线协议细节」挡在可编程层之外,也都在半关闭与错误传播上把协议差异泄漏回运维面。


六、安全与一致性:IR 边界上的两类事故

HTX 作为 IR,还有两个常被配置菜谱忽略的机制后果:

  1. 规范化与重复首部:规则按 HTX 字段读写时,对「同名多首部」「大小写」「连接级 hop-by-hop 首部」的处理必须与出口编码一致。若在错误阶段删除/保留 Connection / Transfer-Encoding 一类字段,故障会表现为上游断流或客户端诡异重试,而不是清晰的 ACL 未命中。
  2. 部分成功的改写:起始行已改、首部改到一半、或 redirect 与 use_backend 同时存在时,分析器阶段顺序决定最终语义(第 6 篇展开)。本篇只强调:HTX 让「半改写」变得可表达——因此也变得需要严格的阶段纪律。

内部文档还描述 payload 区可能环绕、block 区整理(defrag)等行为:这意味着在极端缓冲压力下,改写不只是 CPU,还可能触发整理与二次拷贝。没有实测前,不得把「HTX 一定比旧 raw 路径更慢/更快」写成结论;只能写清可能多出来的工作项。

对平台约束的建议(工程判断):默认允许的动作集应以「首部与起始行」为主;body 检查走专门路径并设硬限制;禁止在热路径上做无界正则扫 body——否则线程模型篇的阻塞警告会以「某条 http-request 规则」的形式再现。


七、谱系、争论与开放问题

谱系:HTTP/1.1(RFC 7230 一系)把消息语义标准化;HTTP/2(RFC 7540 一系)把多路复用变成默认心理模型;HAProxy HTX(Christopher Faulet 等,内部 API 文档)是代理内核侧的工程回答——拒绝 H2→H1 字节桥,改用中立 IR。这与编译器「前端→IR→后端」同构:mux 是前端,规则/分析器是过遍,出口 mux 是后端。

争论

开放问题

  1. Body 级改写与零拷贝 / 快速转发旗标如何共存;平台默认策略应禁止哪些动作。
  2. HTX defrag 在高并发小对象改写下是否成为尾延迟主因——需第 13 篇可观测钩子,本篇不下数字结论。
  3. H3 进入同一 IR 时,传输失败(UDP 阻断)与 HTTP IR 失败如何在排障轴上切开(第 14 篇)。

工程间隙:内部 API 文档更新时间戳可能落后于 3.4.3 小版本;行为以 tag 内源码为准,文档负责讲清动机与块模型。阅读顺序:htx-api.txthtx.chttp_htx.c → 某一侧 mux_h*.c


八、参考资料

规范 / 官方文档(A)

源码(A)

标准谱系(A)

站内对照

实验台账


九、小结

  1. HTX 是版本无关的内部 HTTP IR;mux 负责线协议↔︎HTX,规则负责处理。
  2. 改写/重定向落在 HTX;出口再编码——不要用抓包字节否定内部视图,也不要在 L4 模式上找 HTTP 动作。
  3. H1/H2 在 mux 分叉、在 HTX 汇合;失败粒度与并发模型仍泄漏到运维面。
  4. 相对 L4 的成本是解析、IR、规则与可能的再编码;用能力换税,或把故障域留在正确的层。
  5. IR 边界同时是安全与一致性边界;半改写与 hop-by-hop 首部需要阶段纪律,细节交给第 6 篇。

上一篇:Stream 与 Mux · 系列目录 · 下一篇:ACL / 规则引擎求值

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .