使用Go语言开发网关插件
本文介绍如何使用Go语言开发网关插件。
准备工作
安装Golang和TinyGo两个程序。
Golang
官方指引链接(需为1.18版本以上)。
Windows
下载安装文件。
打开下载好的安装文件双击进行安装,安装成功后,使用键盘上的快捷键Win+R打开运行窗口,在运行窗口中输入
cmd
单击确定即可打开命令窗口,输入命令go version
,成功输出当前安装的版本,表明安装成功。
macOS
下载安装文件。
打开下载好的安装文件双击进行安装,默认会安装到/usr/local/go目录。
打开终端命令行工具,输入命令
go version
,成功输出当前安装的版本,表明安装成功。
Linux
下载安装文件。
执行下列命令进行安装。
安装Golang。
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
配置环境变量。
export PATH=$PATH:/usr/local/go/bin
执行
go version
,成功输出当前安装的版本,表明安装成功。
TinyGo
官方指引链接(固定为0.28.1版本)。
Windows
下载安装文件。
解压安装文件到指定目录。
配置环境变量。如果安装解压后的目录为C:\tinygo,则需要将C:\tinygo\bin添加到环境变量
PATH
中,例如在命令窗口中输入set
命令设置。set PATH=%PATH%;"C:\tinygo\bin";
在命令窗口执行命令
tinygo version
,成功输出当前安装的版本,表明安装成功。
macOS
下载压缩包并解压。
wget https://github.com/tinygo-org/tinygo/releases/download/v0.28.1/tinygo0.28.1.darwin-amd64.tar.gz tar -zxf tinygo0.28.1.darwin-amd64.tar.gz
配置环境变量。如果安装解压后的目录为/tmp,则需要将/tmp/tinygo/bin添加到环境变量
PATH
中。export PATH=/tmp/tinygo/bin:$PATH
在终端执行
tinygo version
,成功输出当前安装的版本,表明安装成功。
Linux
以Ubuntu下amd64架构为例,其他系统请参考官方指引链接。
下载并安装DEB文件。
下载文件:
wget https://github.com/tinygo-org/tinygo/releases/download/v0.28.1/tinygo_0.28.1_amd64.deb
安装文件:
sudo dpkg -i tinygo_0.28.1_amd64.deb
配置环境变量:
export PATH=$PATH:/usr/local/bin
在终端执行
tinygo version
,输出当前安装的版本,表明安装成功。
编写插件
步骤一:初始化工程目录
新建一个工程目录文件,例如wasm-demo-go。
在所建目录下执行以下命令,进行Go工程初始化。
go mod init wasm-demo-go
设置下载依赖包的代理。
go env -w GOPROXY=https://proxy.golang.com.cn,direct
下载构建插件的依赖。
go get github.com/tetratelabs/proxy-wasm-go-sdk go get github.com/alibaba/higress/plugins/wasm-go@main go get github.com/tidwall/gjson
步骤二:编写main.go文件
示例如下所示,可以实现:
在插件配置
mockEnable: true
时直接返回hello world
应答。未做插件配置或者设置
mockEnable: false
时给原始请求添加hello: world
请求头。
更多信息,请参见示例。
在网关控制台中的插件配置为YAML格式,下发给插件时将自动转换为JSON格式,因此示例中的parseConfig
可以直接从JSON中解析配置。
package main
import (
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
// 插件名称
"my-plugin",
// 为解析插件配置,设置自定义函数
wrapper.ParseConfigBy(parseConfig),
// 为处理请求头,设置自定义函数
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
// 自定义插件配置
type MyConfig struct {
mockEnable bool
}
// 在控制台插件配置中填写的YAML配置会自动转换为JSON,此处直接从JSON这个参数里解析配置即可
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {
// 解析出配置,更新到config中
config.mockEnable = json.Get("mockEnable").Bool()
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
proxywasm.AddHttpRequestHeader("hello", "world")
if config.mockEnable {
proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)
}
return types.ActionContinue
}
HTTP处理挂载点
上文示例代码中通过
wrapper.ProcessRequestHeadersBy
将自定义函数onHttpRequestHeaders
用于HTTP请求头处理阶段处理请求。除此之外,还可以通过下面方式,设置其他阶段的自定义处理函数HTTP处理阶段
触发时机
挂载方法
HTTP请求头处理阶段
网关接收到客户端发送来的请求头数据时
wrapper.ProcessRequestHeadersBy
HTTP请求Body处理阶段
网关接收到客户端发送来的请求Body数据时
wrapper.ProcessRequestBodyBy
HTTP应答头处理阶段
网关接收到后端服务响应的应答头数据时
wrapper.ProcessResponseHeadersBy
HTTP应答Body处理阶段
网关接收到后端服务响应的应答Body数据时
wrapper.ProcessResponseBodyBy
工具方法
上文示例代码中的
proxywasm.AddHttpRequestHeader
和proxywasm.SendHttpResponse
是插件SDK提供的两个工具方法,主要的工具方法见下表:分类
方法名称
用途
可以生效的HTTP 处理阶段
请求头处理
GetHttpRequestHeaders
获取客户端请求的全部请求头
HTTP请求头处理阶段
ReplaceHttpRequestHeaders
替换客户端请求的全部请求头
HTTP请求头处理阶段
GetHttpRequestHeader
获取客户端请求的指定请求头
HTTP请求头处理阶段
RemoveHttpRequestHeader
移除客户端请求的指定请求头
HTTP请求头处理阶段
ReplaceHttpRequestHeader
替换客户端请求的指定请求头
HTTP请求头处理阶段
AddHttpRequestHeader
新增一个客户端请求头
HTTP请求头处理阶段
请求Body处理
GetHttpRequestBody
获取客户端请求Body
HTTP请求Body处理阶段
AppendHttpRequestBody
将指定的字节串附加到客户端请求Body末尾
HTTP请求Body处理阶段
PrependHttpRequestBody
将指定的字节串附加到客户端请求Body的开头
HTTP请求Body处理阶段
ReplaceHttpRequestBody
替换客户端请求Body
HTTP请求Body处理阶段
应答头处理
GetHttpResponseHeaders
获取后端响应的全部应答头
HTTP应答头处理阶段
ReplaceHttpResponseHeaders
替换后端响应的全部应答头
HTTP应答头处理阶段
GetHttpResponseHeader
获取后端响应的指定应答头
HTTP应答头处理阶段
RemoveHttpResponseHeader
移除后端响应的指定应答头
HTTP应答头处理阶段
ReplaceHttpResponseHeader
替换后端响应的指定应答头
HTTP应答头处理阶段
AddHttpResponseHeader
新增一个后端响应头
HTTP应答头处理阶段
应答Body处理
GetHttpResponseBody
获取客户端请求Body
HTTP应答Body处理阶段
AppendHttpResponseBody
将指定的字节串附加到后端响应Body末尾
HTTP应答Body处理阶段
PrependHttpResponseBody
将指定的字节串附加到后端响应Body的开头
HTTP应答Body处理阶段
ReplaceHttpResponseBody
替换后端响应Body
HTTP应答Body处理阶段
HTTP调用
DispatchHttpCall
发送一个HTTP请求
-
GetHttpCallResponseHeaders
获取DispatchHttpCall请求响应的应答头
-
GetHttpCallResponseBody
获取DispatchHttpCall请求响应的应答Body
-
GetHttpCallResponseTrailers
获取DispatchHttpCall请求响应的应答Trailer
-
直接响应
SendHttpResponse
直接返回一个特定的HTTP应答
-
流程恢复
ResumeHttpRequest
恢复先前被暂停的请求处理流程
-
ResumeHttpResponse
恢复先前被暂停的应答处理流程
-
步骤三:编译生成WASM文件
执行以下命令编译生成WASM文件。
go mod tidy
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./main.go
编译成功会在当前目录下创建文件main.wasm。该文件在下文本地调试的示例中也会被用到。
在使用云原生网关插件市场的自定义插件功能时,直接上传该文件即可。
本地调试
本示例用到的代码和配置,请参见Demo。
工具准备
安装Docker。
使用docker compose启动验证
进入在编写插件时创建的目录,例如wasm-demo目录,确认该目录下已经编译生成了main.wasm文件。
在目录下创建文件docker-compose.yaml,内容如下:
version: '3.7' services: envoy: image: envoyproxy/envoy:v1.21-latest depends_on: - httpbin networks: - wasmtest ports: - "10000:10000" volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./main.wasm:/etc/envoy/main.wasm httpbin: image: kennethreitz/httpbin:latest networks: - wasmtest ports: - "12345:80" networks: wasmtest: {}
继续在该目录下创建文件envoy.yaml,内容如下:
执行以下命令启动
docker compose
。docker compose up
功能验证
示例
无配置插件
插件无需配置时,直接定义空结构体即可。
package main
import (
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)
func main() {
wrapper.SetCtx(
"hello-world",
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)
return types.ActionContinue
}
在插件中请求外部服务
目前仅支持HTTP调用,支持访问在网关控制台中设置了服务来源的Nacos、K8s服务,以及固定地址或DNS来源的服务。请注意,无法直接使用net/http
库中的HTTP Client,必须使用如下示例中封装的HTTP Client。示例中,在配置解析阶段解析服务类型,生成对应的HTTP Client,在请求头处理阶段根据配置的请求路径访问对应服务,解析应答头,然后再设置在原始的请求头中。