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

L4 网络过滤器链

第 2 周深入"请求怎么被处理"。先看最底层的 L4(TCP 字节流)过滤器:FilterStatus、ReadFilter/WriteFilter 接口、FilterManager 的读写链表,以及迭代/暂停/恢复机制。

📍 第 2 周(过滤器与请求)· 你在这里(从最底层的 L4 字节流开始)
D6 L4 过滤器 D7 HCM D8 L7 过滤器 D9 Router D10 Codec
L01

过滤器链思想

🤔 痛点:网关要做限速、加密、日志、转发……全塞一个大函数里吗? 如果把所有处理写进一个几千行的 handleConnection(),想加一个"限流"就得改这个巨函数,改错一处全崩。而且不同场景要的功能组合还不一样(有的要限流不要日志)。怎么让功能像积木一样自由增删、互不影响?
💡 本质:把处理拆成一节节"工位",串成流水线(今天的类比世界观:机场安检) 答案是过滤器链:数据像行李一样,依次经过一个个独立工位(过滤器),每个工位只干一件事,处理完决定"放行给下一关(Continue)"还是"扣下先别过(StopIteration)"。就像机场安检流水线:一道验登机牌、一道过 X 光、一道人工复查,任何一关都能把你拦下。想加一道安检,插一个工位即可,别的关卡不用动——这就是"可插拔"。
过滤器链 = "流水线" Envoy 处理请求像工厂流水线:数据依次经过一个个"工位"(过滤器),每个工位做一件事(限速、加密、日志、转发…)。每个过滤器处理完,决定"传给下一个工位"(Continue)还是"先停一下"(StopIteration)。好处:功能可组合、可插拔——想加个限流,插一个限流过滤器进链子即可,不用改别的。L4 过滤器处理原始 TCP 字节流,L7 过滤器处理解析后的 HTTP(Day 08)。今天讲 L4。
L02

FilterStatus(迭代控制)

// envoy/network/filter.h:41
enum class FilterStatus {
  Continue,        // 继续调用后续过滤器
  StopIteration    // 停止迭代(等异步操作完成后再 continue)
};
读法:L4 层的迭代控制极简——只有两个值。每个过滤器处理完返回其一:Continue = "我处理好了,交给下一个";StopIteration = "我还没处理完(比如在等一个异步鉴权),先别往下走,我完事了会主动喊继续"。L7 的状态码丰富得多(Day 08)。

👶 小白:返回 StopIteration 之后,被停住的后续过滤器不就永远饿死了吗?

👨‍🏫 老师:不会。StopIteration 是"先暂停"不是"永久取消"。返回它的那个过滤器,一定是在等某件异步的事(比如问外部鉴权服务)。事情办完,它会主动调 continueReading()(L07 会讲),链子就从暂停点接着往下跑。就像安检员对你说"你这箱子要开箱复查,先靠边等一下"——查完了照样放你走,不是把你扔那不管了。真正"拦死"的场景是过滤器直接关连接或返回错误,那是另一回事。

L03

ReadFilter(读路径)

// envoy/network/filter.h:241
class ReadFilter {
  virtual FilterStatus onData(Buffer::Instance& data, bool end_stream) PURE;  // :252 读到数据时调用
  virtual FilterStatus onNewConnection() PURE;                                // :260 连接首次建立
  virtual void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) PURE;  // :273
};
读法:ReadFilter 处理"从下游连接读进来"的数据。onData 是核心(拿到字节缓冲 data,可修改)。onNewConnection 做一次性初始化。回调 ReadFilterCallbacks 提供 continueReading()(恢复被停的链)、injectReadDataToFilterChain()(注入数据)等。HCM(Day 07)就是一个 ReadFilter。
L04

WriteFilter(写路径)

// envoy/network/filter.h:123
class WriteFilter {
  virtual FilterStatus onWrite(Buffer::Instance& data, bool end_stream) PURE;  // :133 数据写往连接时
};
// Filter(:289)同时继承 ReadFilter + WriteFilter,一个实例覆盖读写两路
读法:WriteFilter 处理"往下游连接写出去"的数据(响应方向)。Filter = 读写都管的过滤器。读=下游进来的、写=返回下游的,方向相反。
L05

FIFO vs LIFO

// FilterManager 接口 envoy/network/filter.h:295
addReadFilter()   // :315 FIFO 顺序(先加先调)
addWriteFilter()  // :303 LIFO 顺序(后加先调)
为什么读是 FIFO、写是 LIFO? 想象过滤器 A、B 按顺序配置(A 在前)。读方向(进来):A→B(先配的先处理进入的数据,FIFO)。写方向(出去):B→A(后配的先处理出去的数据,LIFO)。为什么?因为要对称——数据"进来时 A 先看、出去时 A 后看",就像剥洋葱:进去一层层往里剥(A、B),出来一层层往外包(B、A)。比如 A 是加密过滤器:进来时 A 先解密(最外层),出去时 A 最后加密(最外层)。FIFO/LIFO 保证了这种对称的"洋葱模型"。
洋葱模型:进来 A→B(FIFO),出去 B→A(LIFO) 过滤器 A(外层,如加解密) 过滤器 B(内层) 连接核心 读入 A解 → B处理 写出 出去 B先→A后
图注:A 是最外层。进来 A 先剥(解密),出去 A 最后包(加密)——像剥/包洋葱,方向对称,所以读 FIFO、写 LIFO。
📝 举个例子:A=加解密、B=统计字节数 配置顺序 A、B。请求进来(读 FIFO):A 先把密文解成明文 → B 数明文有多少字节。响应出去(写 LIFO):B 先数一遍出去的明文字节 → A 最后把明文加密成密文发走。
如果写方向也用 FIFO(A 先),A 会先加密,B 再去数——B 数到的是密文乱码,统计就错了。正是 LIFO 保证了 B 永远处理明文、A 永远在最外层管密文。
L06

onContinueReading(读迭代核心)

// source/common/network/filter_manager_impl.cc:62 —— L4 读的心脏
for (; entry != upstream_filters_.end(); entry++) {
  if (!(*entry)->initialized_) {          // 首次先调 onNewConnection()
    FilterStatus status = (*entry)->filter_->onNewConnection();
    if (status == FilterStatus::StopIteration) return;
  }
  StreamBuffer read_buffer = buffer_source.getReadBuffer();
  if (read_buffer.buffer.length() > 0 || read_buffer.end_stream) {
    FilterStatus status = (*entry)->filter_->onData(read_buffer.buffer, read_buffer.end_stream);
    if (status == FilterStatus::StopIteration) return;   // 停止即返回
  }
}
读法:就是一个 for 循环遍历读过滤器链,逐个调 onData。任一过滤器返回 StopIterationreturn(中断迭代)。onRead():100)是入口。这个朴素的"遍历 + 可中断"就是过滤器链的执行引擎。
L07

暂停与恢复

某过滤器返回 StopIteration 后,链子暂停。等它异步操作完成,调 continueReading()ActiveReadFilter 回调 parent_.onContinueReading(this, ...))以 this 为起点从 std::next(filter->entry()) 恢复迭代。

为什么要能"暂停"? 有些过滤器要做异步操作——比如调外部鉴权服务问"这个连接允许吗?",这需要时间。它不能傻等(会阻塞整个 worker 线程、拖累其他连接,违背 Day 04 的事件驱动)。所以它返回 StopIteration("我先挂起,别人先处理"),等鉴权结果回来(通过事件循环),再调 continueReading() 从暂停点继续。这就是事件驱动世界里过滤器做异步操作的标准姿势——挂起而非阻塞。连接关闭也有协调机制(disableClose 延迟关闭直到过滤器处理完)。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 过滤器链的"流水线"思想?可插拔的好处?
  • FilterStatus 两个值各是什么意思?
  • ReadFilter/WriteFilter/Filter 各处理什么方向?
  • 读 FIFO、写 LIFO 的"洋葱模型"?
  • 过滤器为什么要能暂停?怎么恢复?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/envoy
sed -n '41,46p' envoy/network/filter.h        # FilterStatus
sed -n '241,281p' envoy/network/filter.h      # ReadFilter
sed -n '62,104p' source/common/network/filter_manager_impl.cc  # onContinueReading
明天预告 · Day 07HTTP 连接管理器 HCM——L7 的心脏。它本身是个 L4 ReadFilter,onData 把字节喂给 codec 解析 HTTP,newStream 为每个请求建 ActiveStream,启动 L7 过滤器链。
← Day 05 请求入口 Day 07 · HTTP 连接管理器 →