diff --git a/plugins/wasm-go/extensions/transformer/README.md b/plugins/wasm-go/extensions/transformer/README.md
index 93f209ce1..5245bb446 100644
--- a/plugins/wasm-go/extensions/transformer/README.md
+++ b/plugins/wasm-go/extensions/transformer/README.md
@@ -74,7 +74,7 @@ description: 请求响应转换插件配置参考
| 添加 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]`) |
+| 去重 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`
`SPLIT_AND_RETAIN_FIRST`: 对值按逗号进行切割,并保留第一个值,如 `k1:"v1,v2,v3"`,去重后得到 `k1:v1`
`SPLIT_AND_RETAIN_LAST`: 对值按逗号进行切割,并保留最后一个值,如 `k1:"v1,v2,v3"`,去重后得到 `k1:v3`
(注:若去重后只剩下一个元素 v1 时,键值对变为 `k1:v1`, 而不是 `k1:[v1]`) |
diff --git a/plugins/wasm-go/extensions/transformer/README_EN.md b/plugins/wasm-go/extensions/transformer/README_EN.md
index a248dfaf1..683413e18 100644
--- a/plugins/wasm-go/extensions/transformer/README_EN.md
+++ b/plugins/wasm-go/extensions/transformer/README_EN.md
@@ -66,7 +66,7 @@ Note:
| 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. |
-| Deduplicate dedupe | Target key | Specified deduplication strategy strategy | `strategy` optional values include:
`RETAIN_UNIQUE`: Retain all unique values in order, e.g., `k1:[v1,v2,v3,v3,v2,v1]`, deduplication results in `k1:[v1,v2,v3]`.
`RETAIN_LAST`: Retain the last value, e.g., `k1:[v1,v2,v3]`, deduplication results in `k1:v3`.
`RETAIN_FIRST` (default): Retain the first value, e.g., `k1:[v1,v2,v3]`, deduplication results in `k1:v1`.
(Note: When deduplication results in only one element v1, the key-value pair becomes `k1:v1`, not `k1:[v1]`.) |
+| Deduplicate dedupe | Target key | Specified deduplication strategy strategy | `strategy` optional values include:
`RETAIN_UNIQUE`: Retain all unique values in order, e.g., `k1:[v1,v2,v3,v3,v2,v1]`, deduplication results in `k1:[v1,v2,v3]`.
`RETAIN_LAST`: Retain the last value, e.g., `k1:[v1,v2,v3]`, deduplication results in `k1:v3`.
`RETAIN_FIRST` (default): Retain the first value, e.g., `k1:[v1,v2,v3]`, deduplication results in `k1:v1`.
`SPLIT_AND_RETAIN_FIRST`: Split the value by comma and retain the first value, e.g., `k1:"v1,v2,v3"`, deduplication results in `k1:v1`.
`SPLIT_AND_RETAIN_LAST`: Split the value by comma and retain the last value, e.g., `k1:"v1,v2,v3"`, deduplication results in `k1:v3`.
(Note: When deduplication results in only one element v1, the key-value pair becomes `k1:v1`, not `k1:[v1]`.) |
## Configuration Example
diff --git a/plugins/wasm-go/extensions/transformer/main.go b/plugins/wasm-go/extensions/transformer/main.go
index c12ad59bc..1822ca83c 100644
--- a/plugins/wasm-go/extensions/transformer/main.go
+++ b/plugins/wasm-go/extensions/transformer/main.go
@@ -1011,7 +1011,15 @@ func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceD
if vs, ok := kvs[key]; ok && len(vs) >= 1 {
kvs[key] = vs[len(vs)-1:]
}
-
+ case "SPLIT_AND_RETAIN_FIRST":
+ if vs, ok := kvs[key]; ok && len(vs) >= 1 {
+ kvs[key] = strings.Split(vs[0], ",")[:1]
+ }
+ case "SPLIT_AND_RETAIN_LAST":
+ if vs, ok := kvs[key]; ok && len(vs) >= 1 {
+ split := strings.Split(vs[0], ",")
+ kvs[key] = split[len(split)-1:]
+ }
case "RETAIN_FIRST":
fallthrough
default:
@@ -1202,7 +1210,20 @@ func (h jsonHandler) handle(host, path string, oriData []byte, mapSourceData map
case "RETAIN_LAST":
dedupedVal = values[len(values)-1].Value() // key: last
-
+ case "SPLIT_AND_RETAIN_FIRST":
+ if len(values) > 0 {
+ split := strings.Split(values[0].String(), ",")
+ if len(split) > 0 {
+ dedupedVal = split[0]
+ }
+ }
+ case "SPLIT_AND_RETAIN_LAST":
+ if len(values) > 0 {
+ split := strings.Split(values[0].String(), ",")
+ if len(split) > 0 {
+ dedupedVal = split[len(split)-1]
+ }
+ }
case "RETAIN_FIRST":
fallthrough
default:
diff --git a/test/e2e/conformance/tests/go-wasm-transformer.go b/test/e2e/conformance/tests/go-wasm-transformer.go
index 33b121656..80f52dee5 100644
--- a/test/e2e/conformance/tests/go-wasm-transformer.go
+++ b/test/e2e/conformance/tests/go-wasm-transformer.go
@@ -632,6 +632,38 @@ var WasmPluginsTransformer = suite.ConformanceTest{
},
},
},
+ {
+ Meta: http.AssertionMeta{
+ TestCaseName: "case 18: request header transformer with split",
+ TargetBackend: "infra-backend-v1",
+ TargetNamespace: "higress-conformance-infra",
+ },
+ Request: http.AssertionRequest{
+ ActualRequest: http.Request{
+ Host: "foo18.com",
+ Path: "/get",
+ RawHeaders: map[string][]string{
+ "X-split-dedupe-first": {"1,2,3"},
+ "X-split-dedupe-last": {"a,b,c"},
+ },
+ },
+ ExpectedRequest: &http.ExpectedRequest{
+ Request: http.Request{
+ Host: "foo18.com",
+ Path: "/get",
+ Headers: map[string]string{
+ "X-split-dedupe-first": "1",
+ "X-split-dedupe-last": "c",
+ },
+ },
+ },
+ },
+ 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 e0f7443a8..47959bfa6 100644
--- a/test/e2e/conformance/tests/go-wasm-transformer.yaml
+++ b/test/e2e/conformance/tests/go-wasm-transformer.yaml
@@ -400,6 +400,26 @@ spec:
port:
number: 8080
---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ annotations:
+ name: wasmplugin-transform-request-header-split
+ namespace: higress-conformance-infra
+spec:
+ ingressClassName: higress
+ rules:
+ - host: "foo18.com"
+ http:
+ paths:
+ - pathType: Prefix
+ path: "/"
+ backend:
+ service:
+ name: infra-backend-v1
+ port:
+ number: 8080
+---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
@@ -860,4 +880,15 @@ spec:
headers:
- key: reroute
newValue: true
+ - ingress:
+ - higress-conformance-infra/wasmplugin-transform-request-header-split
+ configDisable: false
+ config:
+ reqRules:
+ - operate: dedupe
+ headers:
+ - key: X-split-dedupe-first
+ strategy: SPLIT_AND_RETAIN_FIRST
+ - key: X-split-dedupe-last
+ strategy: SPLIT_AND_RETAIN_LAST
url: file:///opt/plugins/wasm-go/extensions/transformer/plugin.wasm
diff --git a/test/e2e/conformance/utils/http/http.go b/test/e2e/conformance/utils/http/http.go
index a48cb1c76..4faf59f30 100644
--- a/test/e2e/conformance/utils/http/http.go
+++ b/test/e2e/conformance/utils/http/http.go
@@ -93,6 +93,7 @@ type Request struct {
Method string
Path string
Headers map[string]string
+ RawHeaders http.Header
Body []byte
ContentType string
UnfollowRedirect bool
@@ -240,6 +241,14 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
}
}
+ if expected.Request.ActualRequest.RawHeaders != nil {
+ for name, values := range expected.Request.ActualRequest.RawHeaders {
+ for _, value := range values {
+ req.Headers[name] = append(req.Headers[name], strings.TrimSpace(value))
+ }
+ }
+ }
+
backendSetHeaders := make([]string, 0, len(expected.Response.AdditionalResponseHeaders))
for name, val := range expected.Response.AdditionalResponseHeaders {
backendSetHeaders = append(backendSetHeaders, name+":"+val)
@@ -755,7 +764,7 @@ func (er *Assertion) GetTestCaseName(i int) string {
headerStr := ""
reqStr := ""
- if er.Request.ActualRequest.Headers != nil {
+ if er.Request.ActualRequest.Headers != nil || er.Request.ActualRequest.RawHeaders != nil {
headerStr = " with headers"
}