Java程序员学习Go开发Higress的WASM插件
契机
⚙ 今年天池大赛有higress相关挑战,研究一下。之前没搞过go,踩了很多坑,最主要的就是tinygo打包,多方寻求解决无果,结论是tinygo@0.32+go@1.19无法在macos arm架构下打包。升级go@1.21再次打包提示unsafe.SliceData requires go1.20 or later。后放弃macos+arm,采用ubuntu+amd64打包,ubuntu打包也必须使用tinygo@0.28.1。0.32.0在ubuntu仍然提示unsupported GOOS/GOARCH pair wasip1/wasm
环境布置
- 包安装等可能需要依赖魔法,实在不行再配置代理仓库吧
- 下载GoLand
- 使用GoLand拷贝代码https://github.com/alibaba/higress
- 定位到higress/plugins/wasm-go/extensions目录
- 安装Golang1.19,推荐直接在IDE中安装
- 访问这个地方https://go.dev/dl/找安装包
- macos下载好的安装文件直接安装,默认会安装到
/usr/local/go
目录
- TinyGo(要求 0.28.1 版本以上)安装
- 官方指引链接:https://tinygo.org/getting-started/install/
- macos直接brew install
- brew tap tinygo-org/tools
- brew install tinygo
- 报错You are using macOS 15…(刚升级15系统
- 下载安装https://tinygo.org/getting-started/install/macos/
- tar xvzf tinygo0.32.0.darwin-amd64.tar.gz
- 添加环境变量
- vim ~/.bash_profile
- 添加一行
export PATH=<extract location>/tinygo/bin:$PATH
- source ~/.bash_profile
官方Demo
- 用GoLand打开higress,找到/plugins/wasm-go/extensions/hello-world
- 使用master分支
- 当前24年6月25日19点,我使用的Revision版本:ef4a0243aceb59ad0e630c40683c429a4f1198a0
- 参考https://higress.io/zh-cn/docs/user/wasm-go/#2-编写-maingo-文件,把hello-world改造下顺便学习下语法
package mainimport ("github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper""github.com/higress-group/proxy-wasm-go-sdk/proxywasm""github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types""github.com/tidwall/gjson"
)func main() {wrapper.SetCtx(// 插件名称"hello-world",// 为解析插件配置,设置自定义函数wrapper.ParseConfigBy(parseConfig),// 为处理请求头,设置自定义函数wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),)
}// MyConfig /*
type MyConfig struct {mockEnable bool
}/*
在控制台插件配置中填写的yaml配置会自动转换为json,此处直接从json这个参数里解析配置即可
@note:- 星号表示指针- 传递一个指针给函数时,函数可以直接修改指针所指向的变量的值
*/
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {// 解析出配置,更新到config中config.mockEnable = json.Get("mockEnable").Bool()log.Info("yml配置此时为:" + json.Raw)return nil
}/*
HTTP 请求头处理阶段,网关接收到客户端发送来的请求头数据时,触发wrapper.ProcessRequestHeadersBy
@note- 没有星号,表示副本,对副本的任意修改都不会影响到原来的变脸
*/
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {log.Info("开始拦截header")//proxywasm工具类直接增加headererr := proxywasm.AddHttpRequestHeader("hello", "world")if err != nil {return 0}//如果配置为的true,直接返回hello-worldif config.mockEnable {//proxywasm工具类直接拦截返回responseerr := proxywasm.SendHttpResponse(200, nil, []byte("hello world"), -1)if err != nil {return 0}}return types.ActionContinue
}
在GoLand中,直接在方法中使用json gjson,会自动在go.mod和main.go的require中增加相关的依赖
higress插件可以实现的4个钩子:
- HTTP 请求头处理阶段:wrapper.ProcessRequestHeadersBy
- HTTP 请求 Body 处理阶段:wrapper.ProcessRequestBodyBy
- HTTP 应答头处理阶段:wrapper.ProcessResponseHeadersBy
- HTTP 应答 Body 处理阶段:wrapper.ProcessResponseBodyBy
proxywasm工具类可以实现的方法如下
分类 | 方法名称 | 用途 | 可以生效的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文件
#整理下代码依赖啥的
go mod tidy
#进入插件目录
cd /higress/plugins/wasm-go/extensions/hello-world#macos打包
/Users/y/GolandProjects/higress/plugins/wasm-go/extensions/hello-world
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer" ./
#提示异常
go: unsupported GOOS/GOARCH pair wasip1/wasm
#升级到go version go1.21.10 darwin/arm64
#打包提示:/Users/y/go/go1.21.10/src/strings/builder.go:49:23: unsafe.SliceData requires go1.20 or later
#todo 多方寻求解决无果,结论是无法在macos arm架构下打包
#todo tinygo只有31版本后才支持darwin,但是插件打包必须是28.1的tinygo#找了ubuntu打包
#必须是这个tinygo版本,31版本任然提示go1.21.10
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
wget https://go.dev/dl/go1.19.linux-amd64.tar.gz
tar -xzf go1.19.linux-amd64.tar.gz
#找个profile放入下面环境变量source一下
export PATH=/home/go/bin:$PATH#unbuntu初始化工程
mkdir /home/go_project_4_compile
cd /home/go_project_4_compile
go mod init wasm-demo-go
#代码复制上去
#整理依赖
go mod tidy
#再打包
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer" ./
#输出main.wasm,下载到本地
本地调试
#创建目录
mkdir ~/higress-plugin-hello
vim docker-compose.yaml
vim envoy.yaml
docker compose up
docker-compose.yaml
version: '3.7'
services:envoy:image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway:v1.4.0-rc.1entrypoint: /usr/local/bin/envoy# 注意这里对wasm开启了debug级别日志,正式部署时则默认info级别command: -c /etc/envoy/envoy.yaml --component-log-level wasm:debugdepends_on:- httpbinnetworks:- wasmtestports:- "10000:10000"volumes:- ./envoy.yaml:/etc/envoy/envoy.yaml- ./main.wasm:/etc/envoy/main.wasmhttpbin:image: kennethreitz/httpbin:latestnetworks:- wasmtestports:- "12345:80"networks:wasmtest: {}
admin:address:socket_address:protocol: TCPaddress: 0.0.0.0port_value: 9901
static_resources:listeners:- name: listener_0address:socket_address:protocol: TCPaddress: 0.0.0.0port_value: 10000filter_chains:- filters:- name: envoy.filters.network.http_connection_managertyped_config:"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerscheme_header_transformation:scheme_to_overwrite: httpsstat_prefix: ingress_httproute_config:name: local_routevirtual_hosts:- name: local_servicedomains: ["*"]routes:- match:prefix: "/"route:cluster: httpbinhttp_filters:- name: wasmdemotyped_config:"@type": type.googleapis.com/udpa.type.v1.TypedStructtype_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasmvalue:config:name: wasmdemovm_config:runtime: envoy.wasm.runtime.v8code:local:filename: /etc/envoy/main.wasmconfiguration:"@type": "type.googleapis.com/google.protobuf.StringValue"value: |{"mockEnable": false}- name: envoy.filters.http.routerclusters:- name: httpbinconnect_timeout: 30stype: LOGICAL_DNS# Comment out the following line to test on v6 networksdns_lookup_family: V4_ONLYlb_policy: ROUND_ROBINload_assignment:cluster_name: httpbinendpoints:- lb_endpoints:- endpoint:address:socket_address:address: httpbinport_value: 80
验证
#10000为网关端口#发送请求
curl -X GET -d '{"args":{},"headers":{"Accept":"*/*","Hello":"world","Host":"127.0.0.1:10000","Original-Host":"127.0.0.1:10000","Req-Start-Time":"1681269273896","User-Agent":"curl/7.79.1","X-Envoy-Expected-Rq-Timeout-Ms":"15000"},"origin":"172.18.0.3","url":"https://127.0.0.1:10000/get"}' http://127.0.0.1:10000/get 返回
{"args": {},"headers": {"Accept": "*/*","Content-Length": "272","Content-Type": "application/x-www-form-urlencoded","Hello": "world","Host": "127.0.0.1:10000","Original-Host": "127.0.0.1:10000","Req-Start-Time": "1719377044522","User-Agent": "curl/8.7.1","X-Envoy-Expected-Rq-Timeout-Ms": "15000"},"origin": "172.22.0.3","url": "https://127.0.0.1:10000/get"
}#修改本地的envoy.yaml
#将mockEnable配置修改为true#重启下higress容器
curl -X GET -d '{"args":{},"headers":{"Accept":"*/*","Hello":"world","Host":"127.0.0.1:10000","Original-Host":"127.0.0.1:10000","Req-Start-Time":"1681269273896","User-Agent":"curl/7.79.1","X-Envoy-Expected-Rq-Timeout-Ms":"15000"},"origin":"172.18.0.3","url":"https://127.0.0.1:10000/get"}' http://127.0.0.1:10000/get返回的
hello world#日志也没问题
[2024-06-26 04:49:29.857][29][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1311] wasm log wasmdemo: [hello-world] 开始拦截header
[2024-06-26 04:49:18.080][29][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1311] wasm log: [hello-world] yml配置此时为:{"mockEnable": true}
总结
tinygo@0.32+go@1.19无法在macos arm架构下打包。升级go@1.21再次打包提示unsafe.SliceData requires go1.20 or later。后放弃macos+arm,采用ubuntu+amd64打包,ubuntu打包也必须使用tinygo@0.28.1。0.32.0在ubuntu仍然提示unsupported GOOS/GOARCH pair wasip1/wasm