Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add try-paths plugin #874

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions plugins/wasm-go/extensions/try-paths/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ARG BUILDER=higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go1.19-tinygo0.28.1-oras1.0.0
FROM $BUILDER as builder

ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct

WORKDIR /workspace

COPY . .

RUN go mod tidy
RUN tinygo build -o /main.wasm -scheduler=none -gc=custom -tags='custommalloc nottinygc_finalizer' -target=wasi ./

FROM scratch as output

COPY --from=builder /main.wasm plugin.wasm

36 changes: 36 additions & 0 deletions plugins/wasm-go/extensions/try-paths/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 功能说明
`try-paths`插件支持请求基于不同的路径进行重试,直到请求到正确返回的请求,功能类似nginx的try files指令。

# 配置字段

| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| serviceSource | string | 必填 | - | k8s,nacos,ip,dns |
| domain | string | 非必填 | - | 服务主机(serviceSource为`dns`必填) |
| host | string | 非必填 | - | 访问的域名地址(serviceSource为`k8s,nacos,ip`填写有效) |
| serviceName | string | 非必填 | - | 服务名称(serviceSource为`k8s,nacos,ip,dns`必填) |
| servicePort | string | 非必填 | - | 服务端口(serviceSource为`k8s,nacos,ip,dns`必填) |
| namespace | string | 非必填 | - | 服务端口(serviceSource为`k8s,nacos`必填) |
| tryPaths | array of string | 必填 | - | 尝试路径,比如index.html,$uri/, index.html等 |
| code | array of int | 非必填 | [403, 404] | 重试状态码,可自定义,默认是403, 404 |


# 配置示例

## 配置了try-paths插件的场景

```yaml
namespace: "default"
serviceName: "oss"
servicePort: 80
serviceSource: "k8s"
host: "<bucket name>.oss-cn-hangzhou.aliyuncs.com"
tryPaths:
- "$uri/"
- "$uri.html"
- "/index.html"

```

基于该配置开启插件,触发插件的请求curl "http://a.com/a", 会依次请求
http://oss.default.svc.cluster.local:80/a/、http://oss.default.svc.cluster.local:80/a.html、http://oss.default.svc.cluster.local:80/index.html, 如果返回码不是默认的404或者403,就会直接返回该请求体,直到重试所有的请求。
18 changes: 18 additions & 0 deletions plugins/wasm-go/extensions/try-paths/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module try-paths

go 1.19

require (
github.com/alibaba/higress/plugins/wasm-go v1.3.5
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226065437-8f7a0b3c9071
github.com/tidwall/gjson v1.17.1
)

require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)
22 changes: 22 additions & 0 deletions plugins/wasm-go/extensions/try-paths/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
github.com/alibaba/higress/plugins/wasm-go v1.3.5 h1:VOLL3m442IHCSu8mR5AZ4sc6LVT9X0w1hdqDI7oB9jY=
github.com/alibaba/higress/plugins/wasm-go v1.3.5/go.mod h1:kr3V9Ntbspj1eSrX8rgjBsdMXkGupYEf+LM72caGPQc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226065437-8f7a0b3c9071 h1:STb5rOHRZOzoiAa+gTz2LFqO1nYj7U/1eIVUJJadU4A=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226065437-8f7a0b3c9071/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
144 changes: 144 additions & 0 deletions plugins/wasm-go/extensions/try-paths/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package main

import (
"errors"
"net/http"
"strings"

"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"
)

// 默认超时时间为60s
var defaultTimeout uint32 = 60000

const VARSTR = "$uri"

func main() {
wrapper.SetCtx(
"try-paths",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}

// 自定义插件配置
type TryPathsConfig struct {
tryPaths []string
code []int // 支持多个返回码
client wrapper.HttpClient
}

func Client(json gjson.Result) (wrapper.HttpClient, error) {
serviceSource := json.Get("serviceSource").String()
serviceName := json.Get("serviceName").String()
host := json.Get("host").String()
servicePort := json.Get("servicePort").Int()
if serviceName == "" || servicePort == 0 {
return nil, errors.New("invalid service config")
}
switch serviceSource {
case "k8s":
namespace := json.Get("namespace").String()
return wrapper.NewClusterClient(wrapper.K8sCluster{
ServiceName: serviceName,
Namespace: namespace,
Port: servicePort,
Host: host,
}), nil
case "nacos":
namespace := json.Get("namespace").String()
return wrapper.NewClusterClient(wrapper.NacosCluster{
ServiceName: serviceName,
NamespaceID: namespace,
Port: servicePort,
Host: host,
}), nil
case "ip":
return wrapper.NewClusterClient(wrapper.StaticIpCluster{
ServiceName: serviceName,
Port: servicePort,
Host: host,
}), nil
case "dns":
domain := json.Get("domain").String()
return wrapper.NewClusterClient(wrapper.DnsCluster{
ServiceName: serviceName,
Port: servicePort,
Domain: domain,
}), nil
default:
return nil, errors.New("unknown service source: " + serviceSource)
}
}

func parseConfig(json gjson.Result, config *TryPathsConfig, log wrapper.Log) error {
// 解析出配置,更新到config中
for _, result := range json.Get("tryPaths").Array() {
config.tryPaths = append(config.tryPaths, result.String())
}

// code默认值为["404", "403"]
if json.Get("code").String() == "" {
config.code = []int{http.StatusNotFound, http.StatusForbidden}
} else {
for _, result := range json.Get("code").Array() {
config.code = append(config.code, int(result.Int()))
}
}
client, err := Client(json)
if err != nil {
return err
}
config.client = client
return nil
}

func convHttpHeadersToStruct(responseHeaders http.Header) [][2]string {
headerStruct := make([][2]string, len(responseHeaders))
i := 0
for key, values := range responseHeaders {
headerStruct[i][0] = key
headerStruct[i][1] = values[0]
i++
}
return headerStruct
}

func included(array []int, value int) bool {
for _, v := range array {
if v == value {
return true
}
}
return false
}

func tryHttpCallback(ctx wrapper.HttpContext, config TryPathsConfig, length int, index int, path string, log wrapper.Log) {
if length == index {
proxywasm.ResumeHttpRequest()
return
}
requestPath := strings.Replace(config.tryPaths[index], VARSTR, path, -1)
config.client.Get(requestPath, nil,
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
if !included(config.code, statusCode) {
proxywasm.SendHttpResponse(uint32(statusCode), convHttpHeadersToStruct(responseHeaders), responseBody, -1)
return
}
tryHttpCallback(ctx, config, length, index+1, path, log)
}, defaultTimeout)
}

func onHttpRequestHeaders(ctx wrapper.HttpContext, config TryPathsConfig, log wrapper.Log) types.Action {
path := ctx.Path()
if len(config.tryPaths) == 0 {
return types.ActionContinue
}

tryHttpCallback(ctx, config, len(config.tryPaths), 0, path, log)
// 需要等待异步回调完成,返回Pause状态,可以被ResumeHttpRequest恢复
return types.ActionPause
}