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(就诊卡),分开管理其状态和 L7 过滤器链。
📝 举个例子:浏览器一条连接同时请求 3 个资源
浏览器用一条 H2 连接并发发出
GET /index.html、GET /app.js、GET /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::encodeHeaders → response_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 08:L7 HTTP 过滤器——丰富的
FilterHeadersStatus、StreamDecoderFilter/StreamEncoderFilter 接口、decode/encode 双向迭代、以及 HIGRESS 定制点。