Day 15 / 共 20 天 · 第 3 周收官
golang-filter
Day 12-14 学的都是 Wasm 插件(隔离、跨语言、热更);今天补上另一套扩展机制:golang-filter——原生 Go 扩展(非 Wasm),编译成 .so 由 Envoy Go filter 动态加载。用于需要常驻 goroutine 维护 SSE 长连接的场景——典型是 MCP 网关。这是第 3 周收官,也把"两套关卡"的取舍讲透。
📍 你在第 3 周(Wasm 插件)的位置 · 收官
D11 插件概览→
D12 wasm-go SDK→
D13 写插件 key-auth→
D14 实战插件→
D15 golang-filter 🎓
💡 先兜住今天(回收 Day 11 的"关卡"类比)
Day 11 说过:Wasm 是"防爆隔间里的标准关卡",golang-filter 是"焊进大楼结构的特殊通道"。今天讲的就是什么时候必须动用这条焊死的通道——当客人要办的不是"检查一下就走"的事,而是"拉一条一直开着的专线"(SSE 长连接,服务器持续往客户端推消息)。Wasm 关卡里的保安不能离岗蹲守(沙箱不许开常驻 goroutine),而焊死通道里的原生 Go 能派一个人专职守着这条专线(goroutine)直到客人离开。MCP 网关就靠这条专线。
L01
为什么要 golang-filter
🤔 痛点:Wasm 这么好,为什么还要再搞一套 golang-filter?
你可能想:Day 11-14 把 Wasm 夸上天了(安全隔离、跨语言、热更),何必再多一套机制、增加复杂度?因为有一类活 Wasm 真干不了——需要"开一个后台线程一直守着"的活。比如 AI 流式响应、MCP 会话,服务器要通过一条 SSE 长连接持续往客户端推消息,这条连接一开就是几分钟。Wasm 沙箱不允许开常驻 goroutine,就卡在这。
图注:一问一答 Wasm 就够了;但"建立连接后持续推 chunk"需要有人常驻守着——这正是 golang-filter 的
api.Running + goroutine 才能做到。Wasm 的局限,golang-filter 来补
Wasm 插件好(隔离、跨语言、热更),但有局限:①沙箱有性能开销;②能力受 proxy-wasm ABI 限制;③不能开常驻 goroutine——而有些场景需要(比如维护一条 SSE 长连接,持续往客户端推数据)。golang-filter 是补充:原生 Go 扩展,编译成
.so,由 Envoy 的 Go filter 动态库加载——无沙箱、性能高、能用完整 Go 生态(redis/gorm)、能开 goroutine 维护长连接。代价:无隔离(崩了影响 Envoy)、要重编进 Envoy 镜像。所以只有 mcp-server/mcp-session 这种"需要长连接"的用它,其他都用 Wasm。L02
注册与构建
// plugins/golang-filter/main.go init 注册两个插件
envoyHttp.RegisterHttpFilterFactoryAndConfigParser(mcp_session.Name, mcp_session.FilterFactory, &Parser{})
envoyHttp.RegisterHttpFilterFactoryAndConfigParser(mcp_server.Name, mcp_server.FilterFactory, &Parser{})
// 依赖 Envoy 官方 Go filter SDK:github.com/envoyproxy/envoy/contrib/golang/...
// 构建:Makefile docker build 产出 golang-filter.so;编进 Gateway 镜像
读法:多个 Go 插件共编到一个
golang-filter.so,Envoy 配置里用 plugin_name 选择哪个。依赖 Envoy 官方的 contrib/golang filter SDK(Higress fork 了 envoy)。需 Higress ≥ 2.1.0。L03
Filter API
// mcp-session/config.go Parser.Parse(:43)解析配置
// 建共享的线程安全 MCPServer(NewMCPServer,:114)
// FilterFactory(:134):每请求造一个 *filter
// mcp-session/filter.go filter struct(:24)内嵌 api.PassThroughStreamFilter
// → 拿默认实现,只覆盖需要的回调
和 Wasm 的 API 不同
golang-filter 用 Envoy 官方的 Go filter API(
api.StreamFilter),和 Wasm 的 wrapper 不同。用"内嵌 PassThroughStreamFilter(透传基类)+ 只覆盖需要的回调"的方式——像 Envoy 课 Day 08 的 L7 过滤器接口的 Go 版。返回码是 api.Continue/StopAndBuffer/LocalReply/Running(比 Wasm 的 Action 多了 Running——表示"我要长期占用这个流",用于 SSE)。Parser 解析配置、FilterFactory 每请求造一个 filter 实例(和 Wasm 的三级 Context 类似)。L04
请求/响应路径
// mcp-session/filter.go
// DecodeHeaders(:50)匹配 match_list → 按 upstream 类型分流
// DecodeData(:163)处理 body(配置写入、JSON-RPC tools/call 限流)
// EncodeHeaders(:208)SSE 场景改 text/event-stream、删 Content-Length
// EncodeData(:236)按 upstream 类型分流
读法:和 Envoy 课 L7 过滤器一样有 Decode(请求方向)/Encode(响应方向)回调。golang-filter 处理 MCP 协议的会话——匹配路由、处理 JSON-RPC 请求、把 SSE 响应改成
text/event-stream。L05
SSE 长连接
// filter.go encodeDataFromRestUpstream(:260)
// 把响应发布到 Redis channel + 调 sseServer.HandleSSE(...) 返回 api.Running(:283)
// OnDestroy(:511):close(f.stopChan) 关闭 SSE goroutine
这就是 golang-filter 相对 Wasm 的杀手锏
MCP 网关需要维护 SSE(Server-Sent Events)长连接——持续往客户端推消息(AI 流式响应、工具调用结果)。Wasm 做不到(不能开常驻 goroutine)。golang-filter 能:返回
api.Running 表示"这个流我要长期持有",然后开一个 goroutine 通过 Redis channel 订阅消息、持续推给客户端。OnDestroy 时 close(stopChan) 优雅关闭 goroutine。这就是为什么 MCP 网关用 golang-filter 而非 Wasm——长连接 + 常驻 goroutine 是 Wasm 沙箱给不了的能力。📝 举个例子:一次 MCP 工具调用的 SSE 生命周期
客户端发起 MCP 会话 →
DecodeHeaders 匹配到 MCP 路由 → EncodeHeaders 把响应头改成 Content-Type: text/event-stream、删掉 Content-Length(长连接没有固定长度)→ 返回 api.Running("这个流我长期占着")→ 开一个 goroutine 订阅 Redis channel,工具每产出一段结果就 HandleSSE 推给客户端 → 客户端断开时 OnDestroy 里 close(stopChan) 关掉 goroutine。整条专线从建立到收尾,全靠那个常驻 goroutine。L06
MCP 网关
mcp-server(filter.go):用 httptest.NewRecorder() 在进程内直接执行 MCP server 逻辑。子目录 servers/(gorm/rag/tool-search/higress-*)和 registry/(nacos)是各类内置 MCP 工具服务器——体现 Higress 作为 MCP 网关把 REST/数据库/RAG 转成 MCP 工具。
Higress 作为 "MCP 网关"
MCP(Model Context Protocol)是 AI Agent 调用工具的协议。Higress 能当"MCP 网关"——把已有的 REST API、数据库(gorm)、RAG 检索、工具搜索等,包装成 MCP 工具服务器暴露给 AI Agent。比如你有个 REST API,Higress 能把它转成 MCP 协议,让 Claude/GPT 等 Agent 直接当工具调用。这是 Higress "AI 原生"的又一体现——不只代理 LLM(Day 16),还能把万物转成 AI 能用的 MCP 工具。用 golang-filter 实现(需要 MCP 会话的长连接)。
L07
Wasm vs golang(总结)
两套机制的最终对比:
• Wasm 插件:隔离、跨语言、热更、绝大多数场景。用 wasm-go SDK 写(Day 12-14)。
• golang-filter:原生 Go、高性能、能开 goroutine 维护长连接、能用完整 Go 生态。用于 MCP 网关等特殊场景。
选择标准:需要长连接/复杂 Go 库/极致性能 → golang-filter;其他一切 → Wasm。MCP 甚至两条路线都有(
• Wasm 插件:隔离、跨语言、热更、绝大多数场景。用 wasm-go SDK 写(Day 12-14)。
• golang-filter:原生 Go、高性能、能开 goroutine 维护长连接、能用完整 Go 生态。用于 MCP 网关等特殊场景。
选择标准:需要长连接/复杂 Go 库/极致性能 → golang-filter;其他一切 → Wasm。MCP 甚至两条路线都有(
pkg/mcp 是 Wasm 版、golang-filter/mcp-server 是 Go 版)——按是否需要长连接选。这体现了 Higress"提供多种扩展手段、按场景选最合适的"的工程务实。| 你的需求 | 选哪套? | 为什么 |
|---|---|---|
| 鉴权 / 限流 / 改写 / 拦截(99% 场景) | Wasm | 要隔离、要热更、想用 Rust/JS 写 |
| SSE 长连接、常驻后台任务 | golang-filter | Wasm 沙箱不能开常驻 goroutine |
| 要用完整 Go 库(gorm/复杂 redis 客户端) | golang-filter | Wasm ABI 能力受限 |
| 追求极致性能、能接受重编 Envoy | golang-filter | 原生 Go 无沙箱开销 |
🧠 一句话口诀
"要长连接 / 要重活 / 要完整 Go 库 → 焊死通道(golang-filter);其余一切 → 防爆隔间(Wasm)。" 拿不准就默认 Wasm——只有撞到"沙箱做不到"的墙,才换 golang-filter。
L08
第 3 周收官 🎓
🧠 第 3 周(Wasm 插件)你已掌握
- Day 11 两套扩展机制、SDK 独立 module、proxy-wasm 绑定、连回 Envoy 课
- Day 12 wasm-go SDK:SetCtx/Option、三级 Context、请求生命周期、RuleMatcher
- Day 13 写插件 key-auth:注册/配置/鉴权/三态语义
- Day 14 实战:Redis+Lua 限流、leader 选举、transformer、跨阶段状态
- Day 15 golang-filter:原生 Go 扩展、SSE 长连接、MCP 网关、两套对比
你现在理解了 Higress 的插件生态——它最活跃、最能体现"AI 原生"的部分。第 4 周聚焦 AI 网关能力(ai-proxy 模型路由、Token 治理)、hgctl、部署,并收官。
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress/plugins/golang-filter
cat main.go
grep -n 'func.*DecodeHeaders\|func.*EncodeData\|api.Running\|HandleSSE' mcp-session/filter.go | head
ls mcp-server/servers/
明天预告 · Day 16(第4周开始):AI 网关能力——Higress 的招牌
ai-proxy 插件:把 39 家 LLM 厂商统一成 OpenAI 协议、模型路由、协议自动转换。这是"AI 原生网关"的核心。