Day 07 / 共 20 天 · 第 2 周 过滤器与请求

HTTP 连接管理器 HCM

HCM(conn_manager_impl.cc,2700+ 行)是 L7 的心脏。它是"L4 到 L7 的桥"——本身是个网络读过滤器,把原始字节解析成 HTTP,为每个请求建流,启动 L7 过滤器链。

📍 第 2 周(过滤器与请求)· 你在这里(L4 字节 → 进入 HTTP 世界的关口)
D6 L4 过滤器 D7 HCM D8 L7 过滤器 D9 Router D10 Codec
L01

HCM 是什么

🤔 痛点:L4 过滤器只看到一堆字节,怎么变成能处理的"HTTP 请求"? Day 06 的 L4 过滤器眼里只有 0x47 0x45 0x54... 这样的原始字节流,它不知道哪里是请求头、哪里是请求体,更不知道一条连接上"混着"好几个请求(HTTP/2)。总得有个东西把字节"翻译"成结构化的 HTTP 请求,还得把混在一起的多个请求分开管理。这个东西就是今天的主角 HCM。
💡 本质:HCM = 医院的"分诊台 + 翻译官"(今天的类比世界观) 延续门卫世界观,HCM 是门内的分诊台:①它是 L4 网络过滤器链的最后一个过滤器(字节走到它这儿);②它把字节翻译成一个个 HTTP 请求(codec 解析);③一条连接上并发来的多个请求(HTTP/2 多路复用),它给每个发一张就诊卡(ActiveStream)分开排队、互不串号;④每个请求走完 L7 流程、拿到响应,它再把响应翻译回字节送出去。它是"字节世界"和"HTTP 世界"之间的唯一关口。
HCM = "HTTP 世界的总调度" 前面 L4 过滤器处理的是原始字节流(不认识 HTTP)。HCM(HTTP Connection Manager)负责把字节流"翻译"成 HTTP 请求,然后管理这些请求的一生:解析、建流、跑 L7 过滤器、收响应、编码回去。它是 Envoy 里最重要、最复杂的一个组件。一条 HTTP/2 连接上可以有多个并发请求(多路复用),HCM 为每个请求建一个"流(stream)"分别管理。理解了 HCM,就理解了 Envoy 怎么处理 HTTP。
L02

它本身是个 L4 ReadFilter

关键设计:HCM 继承 Network::ReadFilter(Day 06 的 L4 读过滤器)——它是网络过滤器链的最后一个(终端)过滤器。initializeReadFilterCallbacks()conn_manager_impl.cc:180)作为 ReadFilter 被安装时调用。

读法:这就是 L4→L7 的桥(Day 05 讲过):网络过滤器链处理字节,走到 HCM 这个"特殊过滤器",它开始说 HTTP。#if defined(HIGRESS):188-203)是 Higress 定制的每 IO 周期请求数限制。

👶 小白:HCM 处理的是 HTTP,怎么反而是个"网络(L4)过滤器"?这不矛盾吗?

👨‍🏫 老师:一点不矛盾,反而很妙。想想数据的形态:从下游连接读进来的本来就是字节,得先有人接住这些字节。HCM 就以"L4 读过滤器"的身份站在网络过滤器链的末端接住字节(这样它天然融入 Day 06 的过滤器机制),然后在自己肚子里把字节喂给 codec 解析成 HTTP,再开启 L7 世界。所以它是"披着 L4 外衣、干着 L7 调度活"的桥。如果单独造一套 L7 入口机制,就没法复用 L4 过滤器链了——这个设计让 L4 和 L7 无缝拼在一条链上。

L03

onData → dispatch

// conn_manager_impl.cc:539 —— HCM 作为 L4 过滤器的 onData
if (!codec_) createCodec(data);              // :549 首次创建 HTTP codec(按 H1/H2/H3)
codec_->dispatch(data);                       // :556 ★核心:把字节交给 codec 解析
// codec 解析过程中回调 HCM 的 newStream / decodeHeaders
return Network::FilterStatus::StopIteration; // :591 HCM 是 L4 链终点,不再往后传
读法:onData 拿到 L4 传来的字节,交给 codec_->dispatch()(HTTP 解析器,Day 10)。codec 边解析边回调HCM——解析出新请求就回调 newStream、解析出请求头就回调 decodeHeaders始终返回 StopIteration(HCM 是 L4 链末端,字节不再往后传给别的 L4 过滤器)。
L04

newStream(新请求)

// conn_manager_impl.cc:435 —— codec 解析到新请求时回调
RequestDecoder& newStream(ResponseEncoder& response_encoder, ...) {
  auto new_stream = ActiveStream(...);       // :455 每个请求一个 ActiveStream
  LinkedList::moveIntoList(std::move(new_stream), streams_);  // :493 加入 streams_ 链表
  return **streams_.begin();                 // ActiveStream 实现了 RequestDecoder
}
一个请求 = 一个 ActiveStream HTTP/2 一条连接能并发多个请求。HCM 为每个请求创建一个 ActiveStream 对象,放进 streams_ 链表管理。每个 ActiveStream 独立持有这个请求的状态:请求头、响应、它自己的 L7 过滤器链(filter_manager_)。这样多个并发请求互不干扰,各走各的过滤器链。连接是"管道",stream 是"管道里并行流动的一个个请求"。
一条 HTTP/2 连接 → HCM 给每个请求发一张"就诊卡"(ActiveStream) 1 条连接3 个并发请求 HCMcodec 解析+分流 ActiveStream #1 · 自己的 L7 链 ActiveStream #2 · 自己的 L7 链 ActiveStream #3 · 自己的 L7 链 streams_ 链表持有这些 ActiveStream,它们各走各的过滤器链,互不串号
图注:HTTP/2 一条连接能并发多个请求。HCM 为每个请求建一个 ActiveStream(就诊卡),分开管理其状态和 L7 过滤器链。
📝 举个例子:浏览器一条连接同时请求 3 个资源 浏览器用一条 H2 连接并发发出 GET /index.htmlGET /app.jsGET /logo.png。codec 解析出 3 个请求,回调 newStream 3 次 → HCM 建 3 个 ActiveStream 放进 streams_。三者并行走各自的 L7 链、各自转发上游;谁先拿到响应就先编码写回,不用等其他两个。这就是 H2"多路复用"省掉"排队等前一个"的关键,全靠 HCM 分流管理。
L05

ActiveStream

ActiveStream 内含一个 filter_manager_FilterManager 成员)——这是 L7 过滤器链的真正持有者(Day 08)。它还实现了 RequestDecoder——codec 解析出的 HTTP 事件(decodeHeaders/decodeData)直接回调到它。

读法:ActiveStream 一身多任:既是"请求的状态容器",又是"codec 的回调接收者(RequestDecoder)",还持有"L7 过滤器链(filter_manager_)"。它是连接 codec 和 L7 过滤器的枢纽。
L06

decodeHeaders(启动 L7 链)

// conn_manager_impl.cc:1352 ActiveStream::decodeHeaders(codec 解析出请求头后回调)
request_headers_ = std::move(headers);       // :1364
// 快照路由配置、过载丢弃检查、100-continue 处理…
refreshCachedRoute();                         // :1565 ★路由匹配在这里做并缓存(Day 05 易错点)
filter_manager_.createDownstreamFilterChain(); // :1576 按配置创建 L7 过滤器链
filter_manager_.decodeHeaders(*request_headers_, end_stream);  // :1612 启动 decode 链迭代
读法:请求头到齐后:①refreshCachedRoute() 算好路由并缓存(路由在 HCM 算,不在 Router!Day 05 强调过)②创建 L7 过滤器链 ③启动 decode 迭代(Day 08)。decodeData/decodeTrailers 类似转发给 filter_manager_。
⚠️ 常见误解:看到"Router 路由过滤器",几乎所有人第一反应是"路由在 Router 里算"。其实路由是在这里(HCM 的 refreshCachedRoute(),解请求头时)就算好并缓存了,Router(Day 09)只是这个缓存去转发。为什么?因为路由结果好几个 L7 过滤器都想用(不只 Router),提前算好大家共享。记牢"HCM 算、Router 读",Day 09 会再验证一次。
L07

响应编码出口

// conn_manager_impl.cc:1940 ActiveStream::encodeHeaders(L7 encode 链走完后回调)
// 尾部(:2087):
response_encoder_->encodeHeaders(headers, end_stream);  // :2088 经 codec 编码写回网络
// encodeData(:2097)→ response_encoder_->encodeData()
请求进、响应出,形成闭环 进来的路:字节 → codec 解析 → decodeHeaders → L7 decode 链 → Router 转发上游。出去的路:上游响应 → L7 encode 链 → ActiveStream::encodeHeadersresponse_encoder_->encodeHeaders(codec 编码成字节)→ 写回下游。HCM 管着这一进一出的完整闭环。decode(解码/请求方向)和 encode(编码/响应方向)是 L7 的两个对称路径——明天详讲。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • HCM 的角色?为什么说它是 L4→L7 的桥?
  • onData 怎么把字节变成 HTTP?(dispatch + 回调)
  • 一个请求对应什么对象?多路复用怎么管理?
  • ActiveStream 一身几任?
  • 路由到底在哪里算?decode/encode 两条路径?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '539,592p' source/common/http/conn_manager_impl.cc   # onData
sed -n '435,495p' source/common/http/conn_manager_impl.cc   # newStream
grep -n 'refreshCachedRoute\|createDownstreamFilterChain\|filter_manager_.decodeHeaders' source/common/http/conn_manager_impl.cc | head
明天预告 · Day 08L7 HTTP 过滤器——丰富的 FilterHeadersStatus、StreamDecoderFilter/StreamEncoderFilter 接口、decode/encode 双向迭代、以及 HIGRESS 定制点。
← Day 06 L4 过滤器 Day 08 · L7 HTTP 过滤器 →