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

L7 HTTP 过滤器

HTTP 过滤器是你写自定义逻辑(认证、限流、改 header)最常打交道的层。今天看它的接口(比 L4 丰富得多)、decode/encode 双向迭代、状态码怎么控制流程。

📍 第 2 周(过滤器与请求)· 你在这里(你写业务插件最常打交道的一层)
D6 L4 过滤器 D7 HCM D8 L7 过滤器 D9 Router D10 Codec
L01

L7 vs L4

🤔 痛点:想"没登录就挡回去",L4 过滤器办得到吗? L4 过滤器(Day 06)眼里只有 TCP 字节流,它不知道哪几个字节是 Authorization 头、哪段是 /admin 路径。要做"检查登录态""按 URL 路径限流""给响应加个头"这类业务逻辑,你需要看到的是解析好的、有语义的 HTTP,而不是一堆生字节。
💡 本质:L7 过滤器 = 拿到"填好的表格"的审批员(今天的类比世界观) 如果说 L4 过滤器面对的是"没拆封的信封",那 HCM(Day 07)拆信解析后,L7 过滤器拿到的就是一张字段清晰的表格:申请人(method)、办哪件事(path)、附带证件(headers)、正文(body)。审批员(L7 过滤器)能精确地看某个字段做决定——证件不全就当场盖章驳回(sendLocalReply(401),不用把表格递到后面部门(上游)。Higress 的绝大多数插件都是这类"审批员"。
L7 过滤器看到的是"结构化的 HTTP" L4 过滤器(Day 06)看到的是原始字节流,啥都不认识。L7 HTTP 过滤器看到的是解析好的 HTTP:请求头(method/path/headers)、请求体、响应头、响应体——结构化、语义清晰。所以能做精细处理:读某个 header 做认证、按 path 限流、给响应加 header、把请求体改写。绝大多数业务网关逻辑(Higress 的插件)都是 L7 过滤器。因为信息更结构化,L7 的接口和状态码也比 L4 丰富得多。
L02

丰富的状态码

envoy/http/filter.hFilterHeadersStatus:38):

状态含义
Continue继续下一个过滤器
StopIteration停止(等异步)
ContinueAndDontEndStream继续但延迟结束流(以便加 body)
StopAllIterationAndBuffer停止并缓冲后续数据
StopAllIterationAndWatermark停止并背压
读法:比 L4 的两个值丰富——因为 HTTP 处理更复杂:有时要停下来缓冲整个 body(StopIterationAndBuffer),有时要背压(StopIterationAndWatermark,配合 Day 19 的水位缓冲)。FilterDataStatus/FilterTrailersStatus 同理。注释极详细,值得逐条读。

👶 小白:L4 就俩状态够用,为啥 L7 要搞出 StopIterationAndBuffer 这么多花样?

👨‍🏫 老师:因为 HTTP 请求"分批到达"且"想整体处理"的需求多。举个例子:一个插件要给上传的图片打水印,它必须拿到完整的请求体才能改。但请求体是一片一片 decodeData 传来的。这时它返回 StopAllIterationAndBuffer——告诉框架"你先帮我把后续 body 全攒住别往下发,攒齐了我一次处理"。L4 只是"字节转发",没有"攒齐一个语义整体"的需求,所以两个状态就够。状态多,是因为 HTTP 层要照顾的情况多。

L03

Decoder / Encoder 过滤器

// StreamDecoderFilter(filter.h:940)—— 请求(解码)方向
virtual FilterHeadersStatus decodeHeaders(RequestHeaderMap& headers, bool end_stream) PURE;  // :948
virtual FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) PURE;           // :957
virtual FilterTrailersStatus decodeTrailers(RequestTrailerMap& trailers) PURE;               // :963

// StreamEncoderFilter(filter.h:1156)—— 响应(编码)方向
virtual FilterHeadersStatus encodeHeaders(ResponseHeaderMap& headers, bool end_stream) PURE; // :1183
virtual FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) PURE;           // :1192

// StreamFilter(filter.h:1226)—— 同时继承二者(双向过滤器)
读法:decode*=处理请求(进来方向)、encode*=处理响应(出去方向)。一个过滤器可以只管一个方向(DecoderFilter)或都管(StreamFilter)。比如认证过滤器只需 decode(检查请求);加响应头的过滤器只需 encode。基类 StreamFilterBase::onDestroy():896)必须清理定时器/异步。
L04

回调操纵流

过滤器通过回调(StreamDecoderFilterCallbacksfilter.h:553)操纵流:continueDecoding()(恢复)、decodingBuffer()(拿缓冲)、addDecodedData()(加数据)、sendLocalReply():669,直接返回本地响应不走上游)、route()(拿路由)、streamInfo()(流信息)。

sendLocalReply:过滤器"截胡"的能力 sendLocalReply() 很关键——它让过滤器能直接生成一个响应返回给客户端,不再转发到上游。比如认证过滤器发现"没登录",直接 sendLocalReply(401) 把请求挡回去,不浪费上游资源。这是网关"拦截"能力的基础:限流拒绝、认证失败、参数非法,都靠它当场返回。HIGRESS 定制了 modifyDecodingBuffer 等重载(filter.h:578-583)供 Wasm 插件改请求体。
📝 举个例子:认证过滤器把没带 token 的请求当场挡回 请求进来:GET /admin,但没有 Authorization 头。
认证过滤器在 decodeHeaders 里发现缺 token → 调 callbacks_->sendLocalReply(401, "unauthorized")直接生成 HTTP/1.1 401 Unauthorized 返回客户端,请求根本不会传给后面的限流过滤器,更不会转发到上游 → 返回 StopIteration 中断 decode 链。
省下了后端的一次无谓处理——这就是"审批员当场驳回"。
L05

decode 迭代

// source/common/http/filter_manager.cc:603 —— L7 请求处理的心脏
for (; entry != decoder_filters_.end(); entry++) {
  FilterHeadersStatus status = (*entry)->decodeHeaders(headers, end_stream);  // :621 调过滤器
  const auto continue_iteration = (*entry)->commonHandleAfterHeadersCallback(status, end_stream); // :643
  if (!continue_iteration && std::next(entry) != decoder_filters_.end()) {
    return;   // 停止迭代,等过滤器调 continueDecoding() 恢复
  }
}
读法:和 L4 一样是"遍历 + 可中断"的循环,只是过滤器是 HTTP 过滤器、状态码更丰富。commonHandleAfterHeadersCallback(L06)把状态码翻译成"继续/停止"。起点由 commonDecodePrefix 决定(从头或从下一个)。
L06

状态→行为映射

// filter_manager.cc:165 commonHandleAfterHeadersCallback
case StopIteration:               iteration_state_ = StopSingleIteration; break;
case StopAllIterationAndBuffer:   iteration_state_ = StopAllBuffer; break;
case ContinueAndDontEndStream:    end_stream = false; headers_continued_ = true; break;
case Continue:                    headers_continued_ = true; break;
// 返回 false(停止)当 stoppedAll() 或 StopIteration
读法:这个函数把过滤器返回的状态码翻译成 FilterManager 的内部迭代状态。Continue→继续;各种 Stop→记下停止模式并中断。这是"状态码"和"迭代控制"之间的转换层,L7 逻辑的关键枢纽。
过滤器返回的状态翻译成的迭代行为什么场景用
Continue标记 headers 已放行,继续下一个过滤器处理完没意见,放行(最常见)
StopIteration停单个(本过滤器),等它 continueDecoding() 恢复要异步查外部鉴权,先挂起
StopAllIterationAndBuffer全停,并把后续 body 攒进缓冲要看完整个请求体才能决定(如改写 body)
ContinueAndDontEndStream继续但先别结束流(end_stream=false准备往请求里补一段 body
读法:把这张表当"字典":过滤器返回什么,FilterManager 就照表做什么。想理解某个插件为何"卡住不往下走",先看它返回了哪个 Stop* 状态。
L07

encode 对称路径

FilterManager::encodeHeaders()filter_manager.cc:1289)——响应方向,结构与 decode 对称,遍历 encoder_filters_,走完后 filter_manager_callbacks_.encodeHeaders():1369)回调到 HCM 的 ActiveStream::encodeHeaders(Day 07),最终 codec 编码写回。

decode 和 encode 是"镜像"的两条路 decode 链:请求进来时走(认证、限流…),顺序 A→B→C→Router。encode 链:响应出去时走(加响应头、压缩…),顺序相反 C→B→A。就像 Day 06 的洋葱模型,只是这里是 HTTP 层。一个 StreamFilter 在 decode 和 encode 各被调一次(进来处理请求、出去处理响应)。Router(Day 09)是 decode 链的终点——它转发到上游、拿回响应、启动 encode 链。
decode 与 encode:一去一回的镜像两条路 认证 A 限流 B 改写 C Router(终端) decode(请求进)A→B→C→Router 认证 A 限流 B 改写 C Router(终端) encode(响应出)Router→C→B→A(逆序) 上游响应折返
图注:同一批过滤器,请求进来正序走 decode、响应出去逆序走 encode(洋葱模型的 HTTP 版)。Router 是折返点。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • L7 过滤器和 L4 的本质区别?
  • 为什么 L7 状态码更丰富?举两个特殊状态。
  • decode 和 encode 各处理什么方向?
  • sendLocalReply 让过滤器能做什么?
  • 状态码怎么翻译成迭代行为?decode/encode 的镜像关系?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '38,110p' envoy/http/filter.h          # FilterHeadersStatus
sed -n '940,990p' envoy/http/filter.h          # StreamDecoderFilter
sed -n '603,680p' source/common/http/filter_manager.cc  # decode 迭代
明天预告 · Day 09Router 路由过滤器——decode 链的终端过滤器。它选路由、选集群、选上游主机、发请求、收响应回灌 encode 链。是"decode 和 encode 的交汇点"。
← Day 07 HCM Day 09 · Router 路由过滤器 →