在 APISIX 中使用 Wasm 插件
APISIX 支持按照 Proxy-Wasm 规范 开发的 WebAssembly (Wasm) 插件,该规范将 Wasm 功能扩展到代理。
使用 Wasm 开发 APISIX 插件有几个好处:
- 能够将许多编程语言编译为 Wasm。这允许你在开发 APISIX 插件时利用现有技术栈的功能。
- 在 APISIX 内本机运行 Wasm 插件,但在单独的 VM 中运行。即使插件崩溃,APISIX 也可以继续运行。
- APISIX 对 Wasm 的持续支持。这避免了维护针对不同编程语言的多个外部插件运行器。
本指南将帮助你了解 APISIX 如何支持 Wasm,以及如何使用 Proxy-Wasm Go SDK 在 Go 中开发示例 Wasm 插件,并将插件加载到 APISIX 中。
APISIX 如何支持 Wasm
APISIX 实现了 Proxy-Wasm 规范。该规范最初是为 Envoy 代理开发的,后来发展成为为代理编写 Wasm 插件的标准。任何使用 Proxy-Wasm SDK 编写的插件都可以在 APISIX 中运行。
APISIX 使用以下编程模型来处理 Wasm 插件。在编写自定义 Wasm 插件时,应实现所有这些接口。

每个插件都有自己的 VMContext,它可以为每条路由创建多个 PluginContext。例如,每个 PluginContext 对应于一个插件实例,因此如果服务配置了 Wasm 插件并且两个路由继承自该服务,则每个路由都将拥有自己的 PluginContext。
同样,一个 PluginContext 是多个 HTTPContext 的父级。每个命中配置的 HTTP 请求也将拥有自己的 HttpContext。例如,如果你同时配置了全局规则和路由,则 HTTP 请求将具有两个 HTTPContext,一个用于来自全局规则的 PluginContext,另一个用于来自路由的 PluginContext。
构建并将 Wasm 插件加载到 APISIX
在本节中,你将学习如何在 Go 中构建一个最小的 Wasm 插件,只要有传入请求,该插件就会在代理中记录一条警告消息,将源代码编译为 Wasm,并将 Wasm 插件加载到 APISIX 中。
前置条件
- 安装 Docker。
- 安装 cURL 以向服务发送请求进行验证。
- 按照 快速入门教程 在 Docker 或 Kubernetes 中启动一个新的 APISIX 实例。
- 安装 Go。
- 安装 TinyGo 将 Go 源代码编译为 Wasm。
- 安装 Proxy-Wasm Go SDK 以在 Go 中开发符合 Proxy-Wasm 的插件。
用 Go 编写插件逻辑
创建一个包含以下代码的文件:
package main
// 导入 proxy-wasm-go-sdk 包以构建 Wasm 插件
import (
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)
func main() {
// 将 VM 上下文设置为 vmContext 结构体的新实例
proxywasm.SetVMContext(&vmContext{})
}
// vmContext 结构体表示 VM 上下文
type vmContext struct {
// 嵌入来自 proxywasm/types 包的 DefaultVMContext 类型
types.DefaultVMContext
}
// 当创建新的插件上下文时调用 NewPluginContext 函数
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
// 返回一个带有指定 contextID 的 pluginContext 结构体新实例
return &pluginContext{contextID: contextID}
}
// pluginContext 结构体表示插件上下文
type pluginContext struct {
// 嵌入来自 proxywasm/types 包的 DefaultPluginContext 类型
types.DefaultPluginContext
conf string
contextID uint32
}
// 当插件启动时调用 OnPluginStart 函数
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
// 获取插件配置数据
data, err := proxywasm.GetPluginConfiguration()
if err!= nil {
// 如果读取配置出错,记录严重错误日志
proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
// 将配置数据存储到 conf 字段
ctx.conf = string(data)
// 返回启动成功状态
return types.OnPluginStartStatusOK
}
// 当插件结束时调用 OnPluginDone 函数
func (ctx *pluginContext) OnPluginDone() bool {
proxywasm.LogInfo("do clean up...")
return true
}
// 当创建新的 HTTP 上下文时调用 NewHttpContext 函数
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
// 返回一个带有指定 contextID 和 conf 的 httpLifecycle 结构体新实例
return &httpLifecycle{
pluginCtxID: ctx.contextID,
conf: ctx.conf,
contextID: contextID,
}
}
// httpLifecycle 结构体表示 HTTP 生命周期
type httpLifecycle struct {
// 嵌入来自 proxywasm/types 包的 DefaultHttpContext 类型
types.DefaultHttpContext
pluginCtxID uint32
contextID uint32
conf string
}
// 当接收到 HTTP 请求头时调用 OnHttpRequestHeaders 函数
func (ctx *httpLifecycle) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
// 记录包含插件上下文 ID、配置以及 HTTP 上下文 ID 的警告日志
proxywasm.LogWarnf("run plugin ctx %d with conf %s in http ctx %d", ctx.pluginCtxID, ctx.conf, ctx.contextID)
// 返回继续处理请求的动作
return types.ActionContinue
}
上面的代码为 httpLifecycle 结构实现了 OnHttpRequestHeaders 方法。只要收到 HTTP 请求头,就会调用该函数。请参阅 [下面的部分](#Proxy-Wasm 回调函数和 APISIX 阶段) 以了解有关 Proxy-Wasm 回调函数与 APISIX 阶段之间相关性的更多信息。
将代码编译为 Wasm
将上述 Go 源代码编译为 .wasm 文件:
tinygo build -o log.go.wasm -scheduler=none -buildmode=wasi-legacy -target=wasi ./main.go
如果编译成功,你应该在当前目录中看到一个 log.go.wasm 文件。