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

原文发布于 quant67.com,转载请保留出处。

上一篇把 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.3 的 src/htx.c、src/http_htx.c、src/mux_h1.c、src/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/规则,二者分离。

工作定义:

  • HTX 消息藏在 buffer 里:中间层(channel / stream-interface)仍操作 buffer,但 mux 与 stream 知道其中是 HTX。
  • 消息由有序 block 组成:元数据(htx_blk)与 payload;payload 与 block 元数据从缓冲区两端相向生长,中间留空隙------细节见内部文档图式。
  • 块类型覆盖起始行、首部、数据、trailer 结束标记等;H2 没有起始行时,由 H2 mux 从伪首部合成 HTX 起始行(方法/路径或 authority、以及文档所述的版本字面等)。
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 的衔接(预告)

  • ACL / 规则(第 6 篇):匹配器读取的样本大量来自 HTX 字段或连接元数据;求值顺序与副作用(set / track / 限流)在 HTX 已就绪之后才有稳定语义。
  • TLS(第 10 篇):终结发生在 xprt/bind,早于或围绕 HTTP 解析;透传则可能根本不进 HTX。SNI 选证书与「HTTP Host 选 backend」是不同层------混为一谈是经典误诊。

相对 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 是后端。

争论:

  • IR 的表达力 vs 转发延迟:块数组 + 可能的整理,是否在「只转发不改写」热路径上可被足够多的快速路径绕开。
  • 是否让规则作者直接碰线格式:可得奇技,但会毁掉 H2/H1 对称并放大安全面------HTX 边界同时是安全边界。

开放问题:

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

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


八、参考资料

规范 / 官方文档(A)

  • HAProxy 3.4.3 , Configuration Manual :mode http、http-request / http-response 动作族(机制落点,非菜谱全集)。
  • haproxy/haproxy v3.4.3 (或同线文档树)内部:doc/internals/api/htx-api.txt --- HTX 背景、块布局、H1/H2 起始行合成、trailer 规则。

源码(A)

  • tag v3.4.3 :src/htx.c、src/http_htx.c、src/mux_h1.c、src/mux_h2.c,及 include/haproxy/htx.h 等。

标准谱系(A)

  • RFC 7230 一系(HTTP/1.1 消息);RFC 7540 / 9113 一系(HTTP/2)------用于理解伪首部与多路复用为何逼出 IR。

站内对照

实验台账

  • 本篇无改写前后的吞吐数字;成本以机制清单给出。

九、小结

  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 / 规则引擎求值

相关推荐
IT大白鼠10 小时前
彭大帅的AI运维助手——自然语言管理 Linux 集群与网络设备——第 2 篇 · 安全守规矩的 AI:分级安全管控是灵魂
linux·运维·人工智能
风华同学10 小时前
免密SSH登录Ubuntu
linux·运维·ubuntu
꯭自꯭闭꯭11 小时前
达梦守护集群手工切换及故障切换
linux·运维·服务器·数据库
cuijiecheng201811 小时前
RK3588 DEP 接入 Dante Domain Manager(DDM)完整测试记录
linux
爱吃香菜的初学者11 小时前
十二.Linux——管道
linux·运维·服务器
网硕互联的小客服19 小时前
Linux服务器磁盘应该如何合理分区?
linux·运维·服务器
贵沫末20 小时前
Ubuntu——常用软件安装
linux·运维·ubuntu
Doraemomo21 小时前
Linux内核驱动开发——中断与定时器
linux·运维·驱动开发
2301_8084143821 小时前
Linux中动静态库的理解
linux·运维·服务器
M78佐菲1 天前
ARM学习笔记(11)
linux·arm开发·笔记·嵌入式硬件·学习