Envoy WASM扩展必须用TinyGo编译,因标准Go依赖不可移植组件;OnHttpRequestHeaders按stream生命周期触发而非每次请求;需显式开启日志、配置和元数据传递。

Envoy WASM 扩展必须用 TinyGo 编译,标准 Go 不行
Envoy 的 WASM 数据平面扩展不支持标准 Go 运行时——它依赖 syscall、net、runtime.GC 等不可移植组件,直接用 go build -o wasm.wasm 会链接失败或运行时 panic。TinyGo 是唯一被 Envoy 官方验证的 Go 编译器,它裁剪了运行时,生成符合 WASI 或 Emscripten ABI 的二进制。
实操建议:
- 安装
tinygo(v0.30+),别用系统包管理器装旧版;执行tinygo version确认输出含wasi支持 - 编译命令固定为:
tinygo build -o filter.wasm -target=wasi ./main.go;-target=wasi不可省略,wasip1或emscripten会导致 Envoy 加载失败 - 避免在代码里调用
log.Printf、time.Now()、os.Getenv—— 这些在 WASI 下未实现,会触发unimplemented syscall错误
HTTP Filter 的 OnHttpRequestHeaders 不是“每次请求都进”,而是按流生命周期触发
很多人以为只要写了 OnHttpRequestHeaders 就能拦截每个 HTTP 请求,结果发现日志没打、逻辑没执行。根本原因是:Envoy 的 WASM Filter 是基于 stream(不是 connection,也不是 request)挂载的,且只有当该 stream 首次收到 headers 时才调用此函数;后续的 OnHttpRequestBody 或重试请求不会重复触发它。
常见错误现象:
立即学习“go语言免费学习笔记(深入)”;
- 改了 header 后 downstream 没生效 → 忘记调用
proxy.SetEffectiveContext或返回了types.ActionContinue却没显式proxy.SendHttpResponseHeaders - 想读完整 body 却只拿到分片 →
OnHttpRequestBody会被多次调用,需自己缓存并检查end_of_stream标志 - 在
OnHttpRequestHeaders里调用proxy.GetConfiguration返回空 → 配置未在 Envoy 的http_filters中通过typed_config正确注入
调试 WASM Filter 时,proxy.Log 默认不输出到 Envoy 日志
写完逻辑加了一堆 proxy.Log,但 docker logs envoy 或 journalctl -u envoy 里完全看不到——因为 Envoy 默认把 WASM 日志级别设为 off,且输出目标不是 stderr/stdout,而是内部 trace sink。
必须手动开启:
- 启动 Envoy 时加参数:
--log-level debug --component-log-level wasm:debug - 在 Envoy 配置的
static_resources.listeners.filter_chains.filters下对应 WASM filter 块中,显式设置:typed_config: { "@type": "type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm", config: { root_id: "...", vm_config: { ... }, configuration: "..." } }—— 注意configuration字段不能为空字符串,否则proxy.GetConfiguration返回 nil -
proxy.Log只接受string,不能传 struct 或 error;想看 error 内容得先err.Error()
Go WASM Filter 无法访问 TLS 信息或原始 socket 地址
Envoy WASM ABI 是严格沙箱化的,所有网络底层能力都被抽象成 stream 接口。这意味着你拿不到 conn.RemoteAddr()、tls.ConnectionState()、甚至无法判断 client 是否用了 HTTP/2 —— 这些信息只能通过 Envoy 的 stream_info 注入,且需提前在配置中启用。
能拿到的元数据有限,但够用:
- 客户端 IP:从
proxy.GetStreamInfo获取,但前提是 Envoy 配置了use_remote_address: true且 XFF 头可信 - TLS 版本/是否加密:需在 Envoy listener 的
filter_chain_match或transport_socket中暴露为 dynamic metadata,再用proxy.GetStreamInfo().GetDynamicMetadata读取 - 真实 host 和 path:优先读
:authority和:pathheader,不要依赖req.Url.Host(WASM 中无 net/http.Request 实例)
真正难的是跨语言上下文传递——比如想把 Go Filter 里生成的 trace ID 透传给下游 gRPC 服务,必须用 proxy.AddHttpRequestHeader 显式注入,Envoy 不会自动桥接 WASM 和 native filter 的 context。










