From 33ce18df5a082bebe1bc739efcf6e88ddda345b5 Mon Sep 17 00:00:00 2001 From: WeixinX Date: Mon, 11 Aug 2025 09:37:35 +0800 Subject: [PATCH] feat(wasm-go): add field reroute to disable route reselection (#2739) --- .../wasm-go/extensions/transformer/README.md | 57 +++++++-- .../extensions/transformer/README_EN.md | 40 ++++++- .../transformer/docker-compose.yaml | 14 +-- .../wasm-go/extensions/transformer/envoy.yaml | 73 +++++++----- .../wasm-go/extensions/transformer/main.go | 16 +++ .../conformance/tests/go-wasm-transformer.go | 57 +++++++++ .../tests/go-wasm-transformer.yaml | 109 ++++++++++++++++++ 7 files changed, 312 insertions(+), 54 deletions(-) diff --git a/plugins/wasm-go/extensions/transformer/README.md b/plugins/wasm-go/extensions/transformer/README.md index 29ae3f962..93f209ce1 100644 --- a/plugins/wasm-go/extensions/transformer/README.md +++ b/plugins/wasm-go/extensions/transformer/README.md @@ -15,10 +15,11 @@ description: 请求响应转换插件配置参考 ## 配置字段 -| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | -| :----: | :----: | :----: | :----: | -------- | -| reqRules | string | 选填,reqRules和respRules至少填一个 | - | 请求转换器配置,指定转换操作类型以及请求头、请求查询参数、请求体的转换规则 | -| respRules | string | 选填,reqRules和respRules至少填一个 | - | 响应转换器配置,指定转换操作类型以及响应头、响应体的转换规则 | +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| :----: |:-------:| :----: |:----:| -------- | +| reroute | boolean | 选填 | true | 是否在请求转换过程中对路由目标进行重新选择 | +| reqRules | string | 选填,reqRules和respRules至少填一个 | - | 请求转换器配置,指定转换操作类型以及请求头、请求查询参数、请求体的转换规则 | +| respRules | string | 选填,reqRules和respRules至少填一个 | - | 响应转换器配置,指定转换操作类型以及响应头、响应体的转换规则 | `reqRules`和`respRules`中每一项的配置字段说明如下: @@ -65,14 +66,14 @@ description: 请求响应转换插件配置参考 ## 转换操作类型 -| 操作类型 | key 字段含义 | value 字段含义 | 描述 | -| :----: | :----: | :----: | ------------------------------------------------------------ | -| 删除 remove | 目标 key |无需设置| 若存在指定的 `key`,则删除;否则无操作 | -| 重命名 rename | 目标 oldKey |新的 key 名称 newKey| 若存在指定的 `oldKey:value`,则将其键名重命名为 `newKey`,得到 `newKey:value`;否则无操作 | -| 更新 replace | 目标 key |新的 value 值 newValue| 若存在指定的 `key:value`,则将其 value 更新为 `newValue`,得到 `key:newValue`;否则无操作 | -| 添加 add | 添加的 key | 添加的 value |若不存在指定的 `key:value`,则添加;否则无操作 | -| 追加 append | 目标 key |追加的 value值 appendValue| 若存在指定的 `key:value`,则追加 appendValue 得到 `key:[value..., appendValue]`;否则相当于执行 add 操作,得到 `key:appendValue` | -| 映射 map | 映射来源 fromKey |映射目标 toKey| 若存在指定的 `fromKey:fromValue`,则将其值 fromValue 映射给 toKey 的值,得到 `toKey:fromValue`,同时保留 `fromKey:fromValue`(注:若 toKey 已存在则其值会被覆盖);否则无操作 | +| 操作类型 | key 字段含义 | value 字段含义 | 描述 | +| :----: | :----: | :----: |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 删除 remove | 目标 key |无需设置| 若存在指定的 `key`,则删除;否则无操作 | +| 重命名 rename | 目标 oldKey |新的 key 名称 newKey| 若存在指定的 `oldKey:value`,则将其键名重命名为 `newKey`,得到 `newKey:value`;否则无操作 | +| 更新 replace | 目标 key |新的 value 值 newValue| 若存在指定的 `key:value`,则将其 value 更新为 `newValue`,得到 `key:newValue`;否则等效于 add 操作 | +| 添加 add | 添加的 key | 添加的 value | 若不存在指定的 `key:value`,则添加;否则无操作 | +| 追加 append | 目标 key |追加的 value值 appendValue| 若存在指定的 `key:value`,则追加 appendValue 得到 `key:[value..., appendValue]`;否则相当于执行 add 操作,得到 `key:appendValue` | +| 映射 map | 映射来源 fromKey |映射目标 toKey| 若存在指定的 `fromKey:fromValue`,则将其值 fromValue 映射给 toKey 的值,得到 `toKey:fromValue`,同时保留 `fromKey:fromValue`(注:若 toKey 已存在则其值会被覆盖);否则无操作 | | 去重 dedupe | 目标 key |指定去重策略 strategy| `strategy` 可选值为:
`RETAIN_UNIQUE`: 按顺序保留所有唯一值,如 `k1:[v1,v2,v3,v3,v2,v1]`,去重后得到 `k1:[v1,v2,v3]`
`RETAIN_LAST`: 保留最后一个值,如 `k1:[v1,v2,v3]`,去重后得到 `k1:v3`
`RETAIN_FIRST` (default): 保留第一个值,如 `k1:[v1,v2,v3]`,去重后得到 `k1:v1`
(注:若去重后只剩下一个元素 v1 时,键值对变为 `k1:v1`, 而不是 `k1:[v1]`) | @@ -410,6 +411,38 @@ $ curl -v -X POST console.higress.io/post -H 'host: foo.bar.com' \ } ``` +#### 禁止重新选择路由目标 + +在请求转换过程中,默认情况下会对路由目标进行重新选择。如果不希望重新选择路由目标,可以设置 `reroute` 为 `false`: + +```yaml +reroute: false +reqRules: + - operate: replace + headers: + - key: reroute + newValue: true +``` + +假设路由配置为: + +- path 前缀匹配 /,header 精准匹配 reroute: false,路由的目标服务响应为 200 "no rerouting" +- path 前缀匹配 /,header 精准匹配 reroute: true,路由的目标服务响应为 200 "rerouting" + +那么根据上述配置,请求得到的结果为: + +```bash +# 插件将 header 从 reroute: false 替换为 reroute: true,但禁止了重新选择路由目标 +$ curl console.higress.io/get -H 'host: foo.bar.com' -H 'reroute: false' +no rerouting% + +# 去掉插件中的 reroute 配置或将其设置为 true,则 +# 插件将 header 从 reroute: false 替换为 reroute: true,并重新选择路由目标 +$ curl console.higress.io/get -H 'host: foo.bar.com' -H 'reroute: false' +rerouting% +``` + + ### Response Transformer 与 Request Transformer 类似,在此仅说明转换 JSON 形式的请求/响应体时的注意事项: diff --git a/plugins/wasm-go/extensions/transformer/README_EN.md b/plugins/wasm-go/extensions/transformer/README_EN.md index 86dd98c87..a248dfaf1 100644 --- a/plugins/wasm-go/extensions/transformer/README_EN.md +++ b/plugins/wasm-go/extensions/transformer/README_EN.md @@ -11,8 +11,9 @@ Plugin execution phase: `authentication phase` Plugin execution priority: `410` ## Configuration Fields -| Name | Data Type | Fill Requirement | Default Value | Description | -| :----: | :----: | :----: | :----: | -------- | +| Name | Data Type | Fill Requirement | Default Value | Description | +| :----: | :----: |:--------------------------------------------------------------:| :----: | -------- | +| reroute | boolean | Optional | true | Whether to reselect the routing target during request transformation | | reqRules | string | Optional, at least one of reqRules or respRules must be filled | - | Request transformer configuration, specifying the transformation operation type and rules for transforming request headers, request query parameters, and request body | | respRules | string | Optional, at least one of reqRules or respRules must be filled | - | Response transformer configuration, specifying the transformation operation type and rules for transforming response headers and response body | @@ -61,7 +62,7 @@ Note: | :----: | :----: | :----: | ------------------------------------------------------------ | | Remove remove | Target key | Not required | If the specified `key` exists, delete it; otherwise, no operation | | Rename rename | Target oldKey | New key name newKey | If the specified `oldKey:value` exists, rename its key to `newKey`, resulting in `newKey:value`; otherwise, no operation | -| Replace replace | Target key | New value newValue | If the specified `key:value` exists, update its value to `newValue`, resulting in `key:newValue`; otherwise, no operation | +| Replace replace | Target key | New value newValue | If the specified `key:value` exists, update its value to `newValue`, resulting in `key:newValue`; otherwise, it is equivalent to performing add operation | | Add add | Added key | Added value | If the specified `key:value` does not exist, add it; otherwise, no operation | | Append append | Target key | Appending value appendValue | If the specified `key:value` exists, append appendValue to get `key:[value..., appendValue]`; otherwise, it is equivalent to performing add operation, resulting in `key:appendValue`. | | Map map | Mapping source fromKey | Mapping target toKey | If the specified `fromKey:fromValue` exists, map its value fromValue to the value of toKey, resulting in `toKey:fromValue`, while retaining `fromKey:fromValue` (note: if toKey already exists, its value will be overwritten); otherwise, no operation. | @@ -361,6 +362,39 @@ $ curl -v -X POST console.higress.io/post -H 'host: foo.bar.com' \ ... } ``` + +#### Prohibit rerouting of the target By default, the target is rerouted during the request transformation process. If you do not want to reroute the target, you can set `reroute` to `false`: + +```yaml +reroute: false +reqRules: + - operate: replace + headers: + - key: reroute + newValue: true +``` + +Assuming the routing configuration is: + +- Path prefix matches /, header exactly matches reroute: false, the target service responds with 200 “no rerouting” +- Path prefix matches /, header exactly matches reroute: true, the target service responds with 200 “rerouting” + +Then, based on the above configuration, the request results are: + +```bash +# The plugin replaces the header from reroute: false to reroute: true, but prohibits re-selecting the route target +$ curl console.higress.io/get -H ‘host: foo.bar.com’ -H ‘reroute: false’ +no rerouting% + +# Remove the reroute configuration from the plugin or set it to true, then +# the plugin will replace the header from reroute: false to reroute: true and re-select the routing target +$ curl console.higress.io/get -H ‘host: foo.bar.com’ -H ‘reroute: false’ +rerouting% +``` + +Translated with DeepL.com (free version) + + ### Response Transformer Similar to Request Transformer, this only describes the precautions for transforming JSON-formatted request/response bodies: diff --git a/plugins/wasm-go/extensions/transformer/docker-compose.yaml b/plugins/wasm-go/extensions/transformer/docker-compose.yaml index 10c46bc53..44db65e41 100644 --- a/plugins/wasm-go/extensions/transformer/docker-compose.yaml +++ b/plugins/wasm-go/extensions/transformer/docker-compose.yaml @@ -1,7 +1,7 @@ version: '3.7' services: envoy: - image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway:v2.0.7 + image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway:v2.1.0 entrypoint: /usr/local/bin/envoy # 注意这里对wasm开启了debug级别日志,正式部署时则默认info级别 command: -c /etc/envoy/envoy.yaml --component-log-level wasm:debug @@ -15,12 +15,12 @@ services: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./plugin.wasm:/etc/envoy/main.wasm - httpbin: - image: kong/httpbin:latest - networks: - - wasmtest - ports: - - "12345:80" +# httpbin: +# image: kong/httpbin:latest +# networks: +# - wasmtest +# ports: +# - "12345:80" networks: wasmtest: {} \ No newline at end of file diff --git a/plugins/wasm-go/extensions/transformer/envoy.yaml b/plugins/wasm-go/extensions/transformer/envoy.yaml index 5ecc5bb47..19debe5a2 100644 --- a/plugins/wasm-go/extensions/transformer/envoy.yaml +++ b/plugins/wasm-go/extensions/transformer/envoy.yaml @@ -28,8 +28,28 @@ static_resources: routes: - match: prefix: "/" - route: - cluster: httpbin + headers: [ + { + name: "reroute", + string_match: { "exact": "false" } + } + ] + direct_response: + status: 200 + body: { inline_string: "no rerouting" } + - match: + prefix: "/" + headers: [ + { + name: "reroute", + string_match: { "exact": "true" } + } + ] + direct_response: + status: 200 + body: { inline_string: "rerouting" } +# route: +# cluster: httpbin http_filters: - name: wasmdemo typed_config: @@ -47,25 +67,14 @@ static_resources: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | { + "reroute": false, "reqRules": [ { "operate": "replace", "headers": [ { - "key": "hello", - "newValue": "higress" - } - ], - "querys": [ - { - "key": "k1", - "newValue": "newQueryV1" - } - ], - "body": [ - { - "key": "k2", - "newValue": "newBodyV2" + "key": "reroute", + "newValue": "true" } ] } @@ -74,19 +83,19 @@ static_resources: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - clusters: - - name: httpbin - connect_timeout: 30s - type: LOGICAL_DNS - # Comment out the following line to test on v6 networks - dns_lookup_family: V4_ONLY - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: httpbin - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: httpbin - port_value: 80 \ No newline at end of file +# clusters: +# - name: httpbin +# connect_timeout: 30s +# type: LOGICAL_DNS +# # Comment out the following line to test on v6 networks +# dns_lookup_family: V4_ONLY +# lb_policy: ROUND_ROBIN +# load_assignment: +# cluster_name: httpbin +# endpoints: +# - lb_endpoints: +# - endpoint: +# address: +# socket_address: +# address: httpbin +# port_value: 80 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/transformer/main.go b/plugins/wasm-go/extensions/transformer/main.go index 26cddf05e..c12ad59bc 100644 --- a/plugins/wasm-go/extensions/transformer/main.go +++ b/plugins/wasm-go/extensions/transformer/main.go @@ -100,6 +100,10 @@ func init() { // // @End type TransformerConfig struct { + // @Title 是否重新路由 + // @Description 是否在请求转换过程中对路由目标进行重新选择,默认为 true + reroute bool `yaml:"reroute"` + // @Title 转换规则 // @Description 指定转换操作类型以及请求/响应头、请求查询参数、请求/响应体参数的转换规则 reqRules []TransformRule `yaml:"reqRules"` @@ -219,6 +223,13 @@ type Param struct { } func parseConfig(json gjson.Result, config *TransformerConfig, log log.Log) (err error) { + reroute := json.Get("reroute") + if !reroute.Exists() { + config.reroute = true + } else { + config.reroute = reroute.Bool() + } + reqRulesInJson := json.Get("reqRules") respRulesInJson := json.Get("respRules") @@ -289,6 +300,11 @@ func constructParam(item gjson.Result, op, valueType string) Param { } func onHttpRequestHeaders(ctx wrapper.HttpContext, config TransformerConfig, log log.Log) types.Action { + if !config.reroute { + log.Debug("disable reroute") + ctx.DisableReroute() + } + // because it may be a response transformer, so the setting of host and path have to advance host, path := ctx.Host(), ctx.Path() ctx.SetContext("host", host) diff --git a/test/e2e/conformance/tests/go-wasm-transformer.go b/test/e2e/conformance/tests/go-wasm-transformer.go index aaa486500..ec0e677b6 100644 --- a/test/e2e/conformance/tests/go-wasm-transformer.go +++ b/test/e2e/conformance/tests/go-wasm-transformer.go @@ -575,6 +575,63 @@ var WasmPluginsTransformer = suite.ConformanceTest{ }, }, }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 16: request reroute", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo16.com", + Path: "/get", + Headers: map[string]string{ + "reroute": "false", + }, + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Host: "foo16.reroute.com", + Path: "/get", + Headers: map[string]string{"reroute": "true"}, + }, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 17: request non reroute", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo17.com", + Path: "/get", + Headers: map[string]string{ + "reroute": "false", + }, + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Host: "foo17.non-reroute.com", + Path: "/get", + // although the header was replaced, it was not rerouted + Headers: map[string]string{"reroute": "true"}, + }, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, } t.Run("WasmPlugin transformer", func(t *testing.T) { for _, testcase := range testcases { diff --git a/test/e2e/conformance/tests/go-wasm-transformer.yaml b/test/e2e/conformance/tests/go-wasm-transformer.yaml index fa2bc91be..e0f7443a8 100644 --- a/test/e2e/conformance/tests/go-wasm-transformer.yaml +++ b/test/e2e/conformance/tests/go-wasm-transformer.yaml @@ -312,6 +312,94 @@ spec: port: number: 8080 --- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasmplugin-transform-request-reroute-root + namespace: higress-conformance-infra + annotations: + higress.io/exact-match-header-reroute: "false" + higress.io/upstream-vhost: "foo16.non-reroute.com" +spec: + ingressClassName: higress + rules: + - host: "foo16.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasmplugin-transform-request-reroute + namespace: higress-conformance-infra + annotations: + higress.io/exact-match-header-reroute: "true" + higress.io/upstream-vhost: "foo16.reroute.com" +spec: + ingressClassName: higress + rules: + - host: "foo16.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasmplugin-transform-request-non-reroute-root + namespace: higress-conformance-infra + annotations: + higress.io/exact-match-header-reroute: "false" + higress.io/upstream-vhost: "foo17.non-reroute.com" +spec: + ingressClassName: higress + rules: + - host: "foo17.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasmplugin-transform-request-non-reroute + namespace: higress-conformance-infra + annotations: + higress.io/exact-match-header-reroute: "true" + higress.io/upstream-vhost: "foo17.reroute.com" +spec: + ingressClassName: higress + rules: + - host: "foo17.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- apiVersion: extensions.higress.io/v1alpha1 kind: WasmPlugin metadata: @@ -751,4 +839,25 @@ spec: body: - key: X-replace-body newValue: exist-body + + - ingress: + - higress-conformance-infra/wasmplugin-transform-request-reroute-root + configDisable: false + config: + reqRules: + - operate: replace + headers: + - key: reroute + newValue: true + + - ingress: + - higress-conformance-infra/wasmplugin-transform-request-non-reroute-root + configDisable: false + config: + reroute: false + reqRules: + - operate: replace + headers: + - key: reroute + newValue: true url: file:///opt/plugins/wasm-go/extensions/transformer/plugin.wasm