mirror of
https://github.com/alibaba/higress.git
synced 2026-02-06 23:21:08 +08:00
1468 lines
43 KiB
Go
1468 lines
43 KiB
Go
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package main
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"regexp"
|
||
|
||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||
"github.com/higress-group/wasm-go/pkg/log"
|
||
"github.com/pkg/errors"
|
||
"github.com/tidwall/gjson"
|
||
"github.com/tidwall/sjson"
|
||
|
||
"github.com/higress-group/wasm-go/pkg/wrapper"
|
||
)
|
||
|
||
func main() {}
|
||
|
||
func init() {
|
||
wrapper.SetCtx(
|
||
"transformer",
|
||
wrapper.ParseConfigBy(parseConfig),
|
||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||
)
|
||
}
|
||
|
||
// @Name transformer
|
||
// @Category custom
|
||
// @Phase UNSPECIFIED_PHASE
|
||
// @Priority 100
|
||
// @Title zh-CN 请求/响应转换器
|
||
// @Title en-US Request/Response Transformer
|
||
// @Description zh-CN transformer 插件可以对请求/响应头、请求查询参数、请求/响应体参数进行转换,支持的转换操作类型包括删除、重命名、更新、添加、追加、映射、去重。
|
||
// @Description en-US The transformer plugin can transform request/response headers, request query parameters, and request/response body parameters. Supported transform operations include remove, rename, replace, add, append, map, and dedupe.
|
||
// @IconUrl https://img.alicdn.com/imgextra/i1/O1CN018iKKih1iVx287RltL_!!6000000004419-2-tps-42-42.png
|
||
// @Version 1.0.0
|
||
//
|
||
// @Contact.name Higress Team
|
||
// @Contact.url http://higress.io/
|
||
// @Contact.email admin@higress.io
|
||
//
|
||
// @Example
|
||
// reqRules:
|
||
// - operate: remove
|
||
// headers:
|
||
// - key: X-remove
|
||
// querys:
|
||
// - key: k1
|
||
// body:
|
||
// - key: a1
|
||
// - operate: rename
|
||
// headers:
|
||
// - oldKey: X-not-renamed
|
||
// newKey: X-renamed
|
||
// - operate: replace
|
||
// headers:
|
||
// - key: X-replace
|
||
// newValue: replaced
|
||
// - operate: add
|
||
// headers:
|
||
// - key: X-add-append
|
||
// value: host-$1
|
||
// host_pattern: ^(.*)\.com$
|
||
// - operate: append
|
||
// headers:
|
||
// - key: X-add-append
|
||
// appendValue: path-$1
|
||
// path_pattern: ^.*?\/(\w+)[\?]{0,1}.*$
|
||
// body:
|
||
// - key: a1-new
|
||
// appendValue: t1-$1-append
|
||
// value_type: string
|
||
// host_pattern: ^(.*)\.com$
|
||
// - operate: map
|
||
// headers:
|
||
// - fromKey: X-add-append
|
||
// toKey: X-map
|
||
// - operate: dedupe
|
||
// headers:
|
||
// - key: X-dedupe-first
|
||
// stratergy: RETAIN_FIRST
|
||
//
|
||
// @End
|
||
type TransformerConfig struct {
|
||
// @Title 是否重新路由
|
||
// @Description 是否在请求转换过程中对路由目标进行重新选择,默认为 true
|
||
reroute bool `yaml:"reroute"`
|
||
|
||
// @Title 转换规则
|
||
// @Description 指定转换操作类型以及请求/响应头、请求查询参数、请求/响应体参数的转换规则
|
||
reqRules []TransformRule `yaml:"reqRules"`
|
||
respRules []TransformRule `yaml:"respRules"`
|
||
|
||
// this field is not exposed to the user and is used to store the request and response transformer instance
|
||
reqTrans Transformer `yaml:"-"`
|
||
respTrans Transformer `yaml:"-"`
|
||
}
|
||
|
||
type TransformRule struct {
|
||
// @Title 转换操作类型
|
||
// @Description 指定转换操作类型,可选值为 remove, rename, replace, add, append, map, dedupe
|
||
operate string `yaml:"operate"`
|
||
|
||
// @Title 映射来源类型
|
||
// @Description map操作可使用该字段进行跨类型映射,可选值为headers, query, body,若yaml中未出现该字段,则默认map操作不做跨类型映射,代码内设定为"self",若yaml中无map操作要求,代码内设定为空字符串
|
||
mapSource string `yaml:"mapSource"`
|
||
|
||
// @Title 请求/响应头转换规则
|
||
// @Description 指定请求/响应头转换规则
|
||
headers []Param `yaml:"headers"`
|
||
|
||
// @Title 请求查询参数转换规则
|
||
// @Description 指定请求查询参数转换规则
|
||
querys []Param `yaml:"querys"`
|
||
|
||
// @Title 请求/响应体参数转换规则
|
||
// @Description 指定请求/响应体参数转换规则,请求体转换允许 content-type 为 application/json, application/x-www-form-urlencoded, multipart/form-data;响应体转换仅允许 content-type 为 application/json
|
||
body []Param `yaml:"body"`
|
||
}
|
||
type RemoveParam struct {
|
||
// @Title 目标key
|
||
// @Description
|
||
key string `yaml:"key"`
|
||
}
|
||
|
||
type RenameParam struct {
|
||
// @Title 目标
|
||
// @Description
|
||
oldKey string `yaml:"oldKey"`
|
||
|
||
// @Title 新的key名称
|
||
// @Description
|
||
newKey string `yaml:"newKey"`
|
||
}
|
||
|
||
type ReplaceParam struct {
|
||
// @Title 目标key
|
||
// @Description
|
||
key string `yaml:"key"`
|
||
|
||
// @Title 新的value值
|
||
// @Description
|
||
newValue string `yaml:"newValue"`
|
||
}
|
||
|
||
type AddParam struct {
|
||
// @Title 添加的key
|
||
// @Description
|
||
key string `yaml:"key"`
|
||
|
||
// @Title 添加的value
|
||
// @Description
|
||
value string `yaml:"value"`
|
||
}
|
||
|
||
type AppendParam struct {
|
||
// @Title 目标key
|
||
// @Description
|
||
key string `yaml:"key"`
|
||
|
||
// @Title 追加的value值
|
||
// @Description
|
||
appendValue string `yaml:"appendValue"`
|
||
}
|
||
|
||
type MapParam struct {
|
||
// @Title 映射来源key
|
||
// @Description
|
||
fromKey string `yaml:"fromKey"`
|
||
|
||
// @Title 映射目标
|
||
// @Description
|
||
toKey string `yaml:"toKey"`
|
||
}
|
||
|
||
type DedupeParam struct {
|
||
// @Title 目标key
|
||
// @Description
|
||
key string `yaml:"key"`
|
||
|
||
// @Title 指定去重策略
|
||
// @Description
|
||
strategy string `yaml:"strategy"`
|
||
}
|
||
|
||
type Param struct {
|
||
removeParam RemoveParam
|
||
renameParam RenameParam
|
||
replaceParam ReplaceParam
|
||
addParam AddParam
|
||
appendParam AppendParam
|
||
mapParam MapParam
|
||
dedupeParam DedupeParam
|
||
// @Title 值类型
|
||
// @Description 当 content-type=application/json 时,为请求/响应体参数指定值类型,可选值为 object, boolean, number, string(default)
|
||
valueType string `yaml:"value_type"`
|
||
|
||
// @Title 请求主机名匹配规则
|
||
// @Description 指定主机名匹配规则,当转换操作类型为 replace, add, append 时有效
|
||
hostPattern string `yaml:"host_pattern"`
|
||
|
||
// @Title 请求路径匹配规则
|
||
// @Description 指定路径匹配规则,当转换操作类型为 replace, add, append 时有效
|
||
pathPattern string `yaml:"path_pattern"`
|
||
}
|
||
|
||
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")
|
||
|
||
if !reqRulesInJson.Exists() && !respRulesInJson.Exists() {
|
||
return errors.New("transformer rule not exist in yaml")
|
||
}
|
||
|
||
if reqRulesInJson.Exists() {
|
||
config.reqRules, err = newTransformRule(reqRulesInJson.Array())
|
||
}
|
||
if respRulesInJson.Exists() {
|
||
config.respRules, err = newTransformRule(respRulesInJson.Array())
|
||
}
|
||
|
||
if err != nil {
|
||
return errors.Wrapf(err, "failed to new transform rule")
|
||
}
|
||
|
||
if config.reqRules != nil {
|
||
config.reqTrans, err = newRequestTransformer(config)
|
||
}
|
||
if config.respRules != nil {
|
||
config.respTrans, err = newResponseTransformer(config)
|
||
}
|
||
if err != nil {
|
||
return errors.Wrapf(err, "failed to new transformer")
|
||
}
|
||
|
||
log.Infof("transform config is: reqRules:%+v, respRules:%+v", config.reqRules, config.respRules)
|
||
|
||
return nil
|
||
}
|
||
|
||
// TODO: 增加检查某些字段比如oldKey&newKey未同时存在时的提示信息
|
||
func constructParam(item gjson.Result, op, valueType string) Param {
|
||
p := Param{
|
||
valueType: valueType,
|
||
}
|
||
|
||
switch op {
|
||
case "remove":
|
||
p.removeParam.key = item.Get("key").String()
|
||
case "rename":
|
||
p.renameParam.oldKey = item.Get("oldKey").String()
|
||
p.renameParam.newKey = item.Get("newKey").String()
|
||
case "replace":
|
||
p.replaceParam.key = item.Get("key").String()
|
||
p.replaceParam.newValue = item.Get("newValue").String()
|
||
case "add":
|
||
p.addParam.key = item.Get("key").String()
|
||
p.addParam.value = item.Get("value").String()
|
||
case "append":
|
||
p.appendParam.key = item.Get("key").String()
|
||
p.appendParam.appendValue = item.Get("appendValue").String()
|
||
case "map":
|
||
p.mapParam.fromKey = item.Get("fromKey").String()
|
||
p.mapParam.toKey = item.Get("toKey").String()
|
||
case "dedupe":
|
||
p.dedupeParam.key = item.Get("key").String()
|
||
p.dedupeParam.strategy = item.Get("strategy").String()
|
||
}
|
||
|
||
if op == "replace" || op == "add" || op == "append" {
|
||
p.hostPattern = item.Get("host_pattern").String()
|
||
p.pathPattern = item.Get("path_pattern").String()
|
||
}
|
||
return p
|
||
}
|
||
|
||
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)
|
||
ctx.SetContext("path", path)
|
||
|
||
if config.reqTrans == nil {
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
|
||
log.Debug("on http request headers ...")
|
||
|
||
headers, err := proxywasm.GetHttpRequestHeaders()
|
||
if err != nil {
|
||
log.Warn("failed to get request headers")
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
hs := convertHeaders(headers)
|
||
if hs[":authority"] == nil {
|
||
log.Warn(errGetRequestHost.Error())
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
if hs[":path"] == nil {
|
||
log.Warn(errGetRequestPath.Error())
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
contentType := ""
|
||
if hs["content-type"] != nil {
|
||
contentType = hs["content-type"][0]
|
||
}
|
||
ctx.SetContext("content-type", contentType)
|
||
|
||
isValidRequestContent := isValidRequestContentType(contentType)
|
||
isBodyChange := config.reqTrans.IsBodyChange()
|
||
needBodyMapSource := config.reqTrans.NeedBodyMapSource()
|
||
|
||
log.Debugf("contentType:%s, isValidRequestContent:%v, isBodyChange:%v, needBodyMapSource:%v",
|
||
contentType, isValidRequestContent, isBodyChange, needBodyMapSource)
|
||
|
||
if isBodyChange && isValidRequestContent {
|
||
delete(hs, "content-length")
|
||
}
|
||
|
||
qs, err := parseQueryByPath(path)
|
||
if err != nil {
|
||
log.Warnf("failed to parse query params by path: %v", err)
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
|
||
ctx.SetContext("headers", hs)
|
||
ctx.SetContext("querys", qs)
|
||
|
||
if !isValidRequestContent || (!isBodyChange && !needBodyMapSource) {
|
||
ctx.DontReadRequestBody()
|
||
} else if needBodyMapSource {
|
||
// we need do transform during body phase
|
||
ctx.SetContext("need_head_trans", struct{}{})
|
||
log.Debug("delay header's transform to body phase")
|
||
return types.HeaderStopIteration
|
||
}
|
||
|
||
mapSourceData := make(map[string]MapSourceData)
|
||
mapSourceData["headers"] = MapSourceData{
|
||
mapSourceType: "headers",
|
||
kvs: hs,
|
||
}
|
||
mapSourceData["querys"] = MapSourceData{
|
||
mapSourceType: "querys",
|
||
kvs: qs,
|
||
}
|
||
|
||
if config.reqTrans.IsHeaderChange() {
|
||
if err = config.reqTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform request headers: %v", err)
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
if config.reqTrans.IsQueryChange() {
|
||
if err = config.reqTrans.TransformQuerys(host, path, qs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform request query params: %v", err)
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
path, err = constructPath(path, qs)
|
||
if err != nil {
|
||
log.Warnf("failed to construct path: %v", err)
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
hs[":path"] = []string{path}
|
||
}
|
||
|
||
headers = reconvertHeaders(hs)
|
||
if err = proxywasm.ReplaceHttpRequestHeaders(headers); err != nil {
|
||
log.Warnf("failed to replace request headers: %v", err)
|
||
ctx.DontReadRequestBody()
|
||
return types.ActionContinue
|
||
}
|
||
|
||
return types.ActionContinue
|
||
}
|
||
|
||
func onHttpRequestBody(ctx wrapper.HttpContext, config TransformerConfig, body []byte, log log.Log) types.Action {
|
||
if config.reqTrans == nil {
|
||
return types.ActionContinue
|
||
}
|
||
|
||
log.Debug("on http request body ...")
|
||
|
||
host, path, err := getHostAndPathFromHttpCtx(ctx)
|
||
if err != nil {
|
||
log.Warn(err.Error())
|
||
return types.ActionContinue
|
||
}
|
||
contentType, ok := ctx.GetContext("content-type").(string)
|
||
if !ok {
|
||
log.Warn(errGetContentType.Error())
|
||
return types.ActionContinue
|
||
}
|
||
structuredBody, err := parseBody(contentType, body)
|
||
if err != nil {
|
||
if !errors.Is(err, errEmptyBody) {
|
||
log.Warnf("failed to parse request body: %v", err)
|
||
}
|
||
log.Debug("request body is empty")
|
||
return types.ActionContinue
|
||
}
|
||
|
||
mapSourceData := make(map[string]MapSourceData)
|
||
var hs map[string][]string
|
||
var qs map[string][]string
|
||
|
||
hs = ctx.GetContext("headers").(map[string][]string)
|
||
if hs == nil {
|
||
log.Warn("failed to get request headers")
|
||
return types.ActionContinue
|
||
}
|
||
if hs[":authority"] == nil {
|
||
log.Warn(errGetRequestHost.Error())
|
||
return types.ActionContinue
|
||
}
|
||
if hs[":path"] == nil {
|
||
log.Warn(errGetRequestPath.Error())
|
||
return types.ActionContinue
|
||
}
|
||
mapSourceData["headers"] = MapSourceData{
|
||
mapSourceType: "headers",
|
||
kvs: hs,
|
||
}
|
||
|
||
qs = ctx.GetContext("querys").(map[string][]string)
|
||
if qs == nil {
|
||
log.Warn("failed to get request querys")
|
||
return types.ActionContinue
|
||
}
|
||
mapSourceData["querys"] = MapSourceData{
|
||
mapSourceType: "querys",
|
||
kvs: qs,
|
||
}
|
||
|
||
switch structuredBody.(type) {
|
||
case map[string]interface{}:
|
||
mapSourceData["body"] = MapSourceData{
|
||
mapSourceType: "bodyJson",
|
||
json: structuredBody.(map[string]interface{})["body"].([]byte),
|
||
}
|
||
case map[string][]string:
|
||
mapSourceData["body"] = MapSourceData{
|
||
mapSourceType: "bodyKv",
|
||
kvs: structuredBody.(map[string][]string),
|
||
}
|
||
}
|
||
|
||
if ctx.GetContext("need_head_trans") != nil {
|
||
if config.reqTrans.IsHeaderChange() {
|
||
if err = config.reqTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform request headers: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
if config.reqTrans.IsQueryChange() {
|
||
if err = config.reqTrans.TransformQuerys(host, path, qs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform request query params: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
path, err = constructPath(path, qs)
|
||
if err != nil {
|
||
log.Warnf("failed to construct path: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
hs[":path"] = []string{path}
|
||
}
|
||
|
||
headers := reconvertHeaders(hs)
|
||
if err = proxywasm.ReplaceHttpRequestHeaders(headers); err != nil {
|
||
log.Warnf("failed to replace request headers: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
if !config.reqTrans.IsBodyChange() {
|
||
return types.ActionContinue
|
||
}
|
||
|
||
if err = config.reqTrans.TransformBody(host, path, structuredBody, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform request body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
|
||
body, err = constructBody(contentType, structuredBody)
|
||
if err != nil {
|
||
log.Warnf("failed to construct request body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
if err = proxywasm.ReplaceHttpRequestBody(body); err != nil {
|
||
log.Warnf("failed to replace request body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
|
||
return types.ActionContinue
|
||
}
|
||
|
||
func onHttpResponseHeaders(ctx wrapper.HttpContext, config TransformerConfig, log log.Log) types.Action {
|
||
if config.respTrans == nil {
|
||
ctx.DontReadResponseBody()
|
||
return types.ActionContinue
|
||
}
|
||
|
||
log.Debug("on http response headers ...")
|
||
|
||
host, path, err := getHostAndPathFromHttpCtx(ctx)
|
||
if err != nil {
|
||
log.Warn(err.Error())
|
||
ctx.DontReadResponseBody()
|
||
return types.ActionContinue
|
||
}
|
||
headers, err := proxywasm.GetHttpResponseHeaders()
|
||
if err != nil {
|
||
log.Warnf("failed to get response headers: %v", err)
|
||
ctx.DontReadResponseBody()
|
||
return types.ActionContinue
|
||
}
|
||
hs := convertHeaders(headers)
|
||
ctx.SetContext("headers", hs)
|
||
contentType := ""
|
||
if hs["content-type"] != nil {
|
||
contentType = hs["content-type"][0]
|
||
}
|
||
ctx.SetContext("content-type", contentType)
|
||
|
||
isValidResponseContent := isValidResponseContentType(contentType)
|
||
isBodyChange := config.respTrans.IsBodyChange()
|
||
needBodyMapSource := config.respTrans.NeedBodyMapSource()
|
||
|
||
if isBodyChange && isValidResponseContent {
|
||
delete(hs, "content-length")
|
||
}
|
||
|
||
if !isValidResponseContent || (!isBodyChange && !needBodyMapSource) {
|
||
ctx.DontReadResponseBody()
|
||
} else if needBodyMapSource {
|
||
// we need do transform during body phase
|
||
ctx.SetContext("need_head_trans", struct{}{})
|
||
return types.HeaderStopIteration
|
||
}
|
||
|
||
mapSourceData := make(map[string]MapSourceData)
|
||
mapSourceData["headers"] = MapSourceData{
|
||
mapSourceType: "headers",
|
||
kvs: hs,
|
||
}
|
||
|
||
if config.respTrans.IsHeaderChange() {
|
||
if err = config.respTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform response headers: %v", err)
|
||
ctx.DontReadResponseBody()
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
headers = reconvertHeaders(hs)
|
||
if err = proxywasm.ReplaceHttpResponseHeaders(headers); err != nil {
|
||
log.Warnf("failed to replace response headers: %v", err)
|
||
ctx.DontReadResponseBody()
|
||
return types.ActionContinue
|
||
}
|
||
|
||
return types.ActionContinue
|
||
}
|
||
|
||
func onHttpResponseBody(ctx wrapper.HttpContext, config TransformerConfig, body []byte, log log.Log) types.Action {
|
||
if config.respTrans == nil {
|
||
return types.ActionContinue
|
||
}
|
||
|
||
log.Debug("on http response body ...")
|
||
|
||
host, path, err := getHostAndPathFromHttpCtx(ctx)
|
||
if err != nil {
|
||
log.Warn(err.Error())
|
||
return types.ActionContinue
|
||
}
|
||
contentType, ok := ctx.GetContext("content-type").(string)
|
||
if !ok {
|
||
log.Warn(errGetContentType.Error())
|
||
return types.ActionContinue
|
||
}
|
||
structuredBody, err := parseBody(contentType, body)
|
||
if err != nil {
|
||
if !errors.Is(err, errEmptyBody) {
|
||
log.Warnf("failed to parse response body: %v", err)
|
||
}
|
||
log.Debug("response body is empty")
|
||
return types.ActionContinue
|
||
}
|
||
|
||
mapSourceData := make(map[string]MapSourceData)
|
||
var hs map[string][]string
|
||
|
||
hs = ctx.GetContext("headers").(map[string][]string)
|
||
if hs == nil {
|
||
log.Warn("failed to get response headers")
|
||
return types.ActionContinue
|
||
}
|
||
mapSourceData["headers"] = MapSourceData{
|
||
mapSourceType: "headers",
|
||
kvs: hs,
|
||
}
|
||
|
||
switch structuredBody.(type) {
|
||
case map[string]interface{}:
|
||
mapSourceData["body"] = MapSourceData{
|
||
mapSourceType: "bodyJson",
|
||
json: structuredBody.(map[string]interface{})["body"].([]byte),
|
||
}
|
||
case map[string][]string:
|
||
mapSourceData["body"] = MapSourceData{
|
||
mapSourceType: "bodyKv",
|
||
kvs: structuredBody.(map[string][]string),
|
||
}
|
||
}
|
||
|
||
if ctx.GetContext("need_head_trans") != nil {
|
||
if config.respTrans.IsHeaderChange() {
|
||
if err = config.respTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform response headers: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
headers := reconvertHeaders(hs)
|
||
if err = proxywasm.ReplaceHttpResponseHeaders(headers); err != nil {
|
||
log.Warnf("failed to replace response headers: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
}
|
||
|
||
if !config.respTrans.IsBodyChange() {
|
||
return types.ActionContinue
|
||
}
|
||
|
||
if err = config.respTrans.TransformBody(host, path, structuredBody, mapSourceData); err != nil {
|
||
log.Warnf("failed to transform response body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
|
||
body, err = constructBody(contentType, structuredBody)
|
||
if err != nil {
|
||
log.Warnf("failed to construct response body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
if err = proxywasm.ReplaceHttpResponseBody(body); err != nil {
|
||
log.Warnf("failed to replace response body: %v", err)
|
||
return types.ActionContinue
|
||
}
|
||
|
||
return types.ActionContinue
|
||
}
|
||
|
||
func getHostAndPathFromHttpCtx(ctx wrapper.HttpContext) (host, path string, err error) {
|
||
host, ok := ctx.GetContext("host").(string)
|
||
if !ok {
|
||
return "", "", errGetRequestHost
|
||
}
|
||
path, ok = ctx.GetContext("path").(string)
|
||
if !ok {
|
||
return "", "", errGetRequestPath
|
||
}
|
||
return host, path, nil
|
||
}
|
||
|
||
func newTransformRule(rules []gjson.Result) (res []TransformRule, err error) {
|
||
|
||
for _, r := range rules {
|
||
var tRule TransformRule
|
||
tRule.operate = strings.ToLower(r.Get("operate").String())
|
||
if !isValidOperation(tRule.operate) {
|
||
errors.Wrapf(err, "invalid operate type %q", tRule.operate)
|
||
return
|
||
}
|
||
|
||
if tRule.operate == "map" {
|
||
mapSourceInJson := r.Get("mapSource")
|
||
if !mapSourceInJson.Exists() {
|
||
tRule.mapSource = "self"
|
||
} else {
|
||
tRule.mapSource = mapSourceInJson.String()
|
||
if !isValidMapSource(tRule.mapSource) {
|
||
errors.Wrapf(err, "invalid map source %q", tRule.mapSource)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, h := range r.Get("headers").Array() {
|
||
tRule.headers = append(tRule.headers, constructParam(h, tRule.operate, ""))
|
||
}
|
||
for _, q := range r.Get("querys").Array() {
|
||
tRule.querys = append(tRule.querys, constructParam(q, tRule.operate, ""))
|
||
}
|
||
for _, b := range r.Get("body").Array() {
|
||
valueType := strings.ToLower(b.Get("value_type").String())
|
||
if valueType == "" { // default
|
||
valueType = "string"
|
||
}
|
||
if !isValidJsonType(valueType) {
|
||
errors.Wrapf(err, "invalid body params type %q", valueType)
|
||
return
|
||
}
|
||
tRule.body = append(tRule.body, constructParam(b, tRule.operate, valueType))
|
||
}
|
||
res = append(res, tRule)
|
||
}
|
||
return
|
||
}
|
||
|
||
type Transformer interface {
|
||
TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error
|
||
TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error
|
||
TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error
|
||
IsHeaderChange() bool
|
||
IsQueryChange() bool
|
||
IsBodyChange() bool
|
||
NeedBodyMapSource() bool
|
||
}
|
||
|
||
var _ Transformer = (*requestTransformer)(nil)
|
||
var _ Transformer = (*responseTransformer)(nil)
|
||
|
||
type requestTransformer struct {
|
||
headerHandler *kvHandler
|
||
queryHandler *kvHandler
|
||
bodyHandler *requestBodyHandler
|
||
isHeaderChange bool
|
||
isQueryChange bool
|
||
isBodyChange bool
|
||
needBodyMapSource bool
|
||
}
|
||
|
||
func newRequestTransformer(config *TransformerConfig) (Transformer, error) {
|
||
headerKvtGroup, isHeaderChange, _, err := newKvtGroup(config.reqRules, "headers")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to new kvt group for headers")
|
||
}
|
||
queryKvtGroup, isQueryChange, _, err := newKvtGroup(config.reqRules, "querys")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to new kvt group for querys")
|
||
}
|
||
bodyKvtGroup, isBodyChange, _, err := newKvtGroup(config.reqRules, "body")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to new kvt group for body")
|
||
}
|
||
|
||
bodyMapSource := bodyMapSourceInRule(config.reqRules)
|
||
|
||
return &requestTransformer{
|
||
headerHandler: &kvHandler{headerKvtGroup},
|
||
queryHandler: &kvHandler{queryKvtGroup},
|
||
bodyHandler: &requestBodyHandler{
|
||
formDataHandler: &kvHandler{bodyKvtGroup},
|
||
jsonHandler: &jsonHandler{bodyKvtGroup},
|
||
},
|
||
isHeaderChange: isHeaderChange,
|
||
isQueryChange: isQueryChange,
|
||
isBodyChange: isBodyChange,
|
||
needBodyMapSource: bodyMapSource,
|
||
}, nil
|
||
}
|
||
|
||
func (t requestTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error {
|
||
return t.headerHandler.handle(host, path, hs, mapSourceData)
|
||
}
|
||
|
||
func (t requestTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error {
|
||
return t.queryHandler.handle(host, path, qs, mapSourceData)
|
||
}
|
||
|
||
func (t requestTransformer) TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error {
|
||
switch body.(type) {
|
||
case map[string][]string:
|
||
return t.bodyHandler.formDataHandler.handle(host, path, body.(map[string][]string), mapSourceData)
|
||
|
||
case map[string]interface{}:
|
||
m := body.(map[string]interface{})
|
||
newBody, err := t.bodyHandler.handle(host, path, m["body"].([]byte), mapSourceData)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
m["body"] = newBody
|
||
|
||
default:
|
||
return errBodyType
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (t requestTransformer) IsHeaderChange() bool { return t.isHeaderChange }
|
||
func (t requestTransformer) IsQueryChange() bool { return t.isQueryChange }
|
||
func (t requestTransformer) IsBodyChange() bool { return t.isBodyChange }
|
||
func (t requestTransformer) NeedBodyMapSource() bool { return t.needBodyMapSource }
|
||
|
||
type responseTransformer struct {
|
||
headerHandler *kvHandler
|
||
bodyHandler *responseBodyHandler
|
||
isHeaderChange bool
|
||
isBodyChange bool
|
||
needBodyMapSource bool
|
||
}
|
||
|
||
func newResponseTransformer(config *TransformerConfig) (Transformer, error) {
|
||
|
||
headerKvtGroup, isHeaderChange, _, err := newKvtGroup(config.respRules, "headers")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to new kvt group for headers")
|
||
}
|
||
bodyKvtGroup, isBodyChange, _, err := newKvtGroup(config.respRules, "body")
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "failed to new kvt group for body")
|
||
}
|
||
bodyMapSource := bodyMapSourceInRule(config.respRules)
|
||
|
||
return &responseTransformer{
|
||
headerHandler: &kvHandler{headerKvtGroup},
|
||
bodyHandler: &responseBodyHandler{&jsonHandler{bodyKvtGroup}},
|
||
isHeaderChange: isHeaderChange,
|
||
isBodyChange: isBodyChange,
|
||
needBodyMapSource: bodyMapSource,
|
||
}, nil
|
||
}
|
||
|
||
func (t responseTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error {
|
||
return t.headerHandler.handle(host, path, hs, mapSourceData)
|
||
}
|
||
|
||
func (t responseTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error {
|
||
// the response does not need to transform the query params, always returns nil
|
||
return nil
|
||
}
|
||
|
||
func (t responseTransformer) TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error {
|
||
switch body.(type) {
|
||
case map[string]interface{}:
|
||
m := body.(map[string]interface{})
|
||
newBody, err := t.bodyHandler.handle(host, path, m["body"].([]byte), mapSourceData)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
m["body"] = newBody
|
||
|
||
default:
|
||
return errBodyType
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (t responseTransformer) IsHeaderChange() bool { return t.isHeaderChange }
|
||
func (t responseTransformer) IsQueryChange() bool { return false } // the response does not need to transform the query params, always returns false
|
||
func (t responseTransformer) IsBodyChange() bool { return t.isBodyChange }
|
||
func (t responseTransformer) NeedBodyMapSource() bool { return t.needBodyMapSource }
|
||
|
||
type requestBodyHandler struct {
|
||
formDataHandler *kvHandler
|
||
*jsonHandler
|
||
}
|
||
|
||
type responseBodyHandler struct {
|
||
*jsonHandler
|
||
}
|
||
|
||
type kvHandler struct {
|
||
kvtOps []kvtOperation
|
||
}
|
||
|
||
type jsonHandler struct {
|
||
kvtOps []kvtOperation
|
||
}
|
||
|
||
func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceData map[string]MapSourceData) error {
|
||
// arbitary order. for example: remove → rename → replace → add → append → map → dedupe
|
||
|
||
for _, kvtOp := range h.kvtOps {
|
||
switch kvtOp.kvtOpType {
|
||
case RemoveK:
|
||
// remove
|
||
for _, remove := range kvtOp.removeKvtGroup {
|
||
delete(kvs, remove.key)
|
||
}
|
||
case RenameK:
|
||
// rename: 若指定 oldKey 不存在则无操作;否则将 oldKey 的值追加给 newKey,并删除 oldKey:value
|
||
for _, rename := range kvtOp.renameKvtGroup {
|
||
oldKey, newKey := rename.oldKey, rename.newKey
|
||
if ovs, ok := kvs[oldKey]; ok {
|
||
kvs[newKey] = append(kvs[newKey], ovs...)
|
||
delete(kvs, oldKey)
|
||
}
|
||
}
|
||
case ReplaceK:
|
||
// replace: 若指定 key 不存在,相当于添加操作;否则替换 value 为 newValue
|
||
for _, replace := range kvtOp.replaceKvtGroup {
|
||
key, newValue := replace.key, replace.newValue
|
||
if replace.reg != nil {
|
||
newValue = replace.reg.matchAndReplace(newValue, host, path)
|
||
}
|
||
kvs[key] = []string{newValue}
|
||
}
|
||
case AddK:
|
||
// add: 若指定 key 存在则无操作;否则添加 key:value
|
||
for _, add := range kvtOp.addKvtGroup {
|
||
key, value := add.key, add.value
|
||
if _, ok := kvs[key]; ok {
|
||
continue
|
||
}
|
||
if add.reg != nil {
|
||
value = add.reg.matchAndReplace(value, host, path)
|
||
}
|
||
kvs[key] = []string{value}
|
||
}
|
||
|
||
case AppendK:
|
||
// append: 若指定 key 存在,则追加同名 kv;否则相当于添加操作
|
||
for _, append_ := range kvtOp.appendKvtGroup {
|
||
key, appendValue := append_.key, append_.appendValue
|
||
if append_.reg != nil {
|
||
appendValue = append_.reg.matchAndReplace(appendValue, host, path)
|
||
}
|
||
kvs[key] = append(kvs[key], appendValue)
|
||
}
|
||
case MapK:
|
||
// map: 若指定 fromKey 不存在则无操作;否则将 fromKey 的值映射给 toKey 的值
|
||
for _, map_ := range kvtOp.mapKvtGroup {
|
||
fromKey, toKey := map_.fromKey, map_.toKey
|
||
if kvtOp.mapSource == "headers" {
|
||
fromKey = strings.ToLower(fromKey)
|
||
}
|
||
source, exist := mapSourceData[kvtOp.mapSource]
|
||
if !exist {
|
||
proxywasm.LogWarnf("map key failed, source:%s not exists", kvtOp.mapSource)
|
||
continue
|
||
}
|
||
proxywasm.LogDebugf("search key:%s in source:%s", fromKey, kvtOp.mapSource)
|
||
if fromValue, ok := source.search(fromKey); ok {
|
||
switch source.mapSourceType {
|
||
case "headers", "querys", "bodyKv":
|
||
kvs[toKey] = fromValue.([]string)
|
||
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
|
||
|
||
case "bodyJson":
|
||
if valueJson, ok := fromValue.(gjson.Result); ok {
|
||
valueStr := valueJson.String()
|
||
if valueStr != "" {
|
||
kvs[toKey] = []string{valueStr}
|
||
proxywasm.LogDebugf("map key:%s to key:%s success, values is:%s", fromKey, toKey, valueStr)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
case DedupeK:
|
||
// dedupe: 根据 strategy 去重:RETAIN_UNIQUE 保留所有唯一值,RETAIN_LAST 保留最后一个值,RETAIN_FIRST 保留第一个值 (default)
|
||
for _, dedupe := range kvtOp.dedupeKvtGroup {
|
||
key, strategy := dedupe.key, dedupe.strategy
|
||
switch strings.ToUpper(strategy) {
|
||
case "RETAIN_UNIQUE":
|
||
uniSet, uniques := make(map[string]struct{}), make([]string, 0)
|
||
for _, v := range kvs[key] {
|
||
if _, ok := uniSet[v]; !ok {
|
||
uniSet[v] = struct{}{}
|
||
uniques = append(uniques, v)
|
||
}
|
||
}
|
||
kvs[key] = uniques
|
||
|
||
case "RETAIN_LAST":
|
||
if vs, ok := kvs[key]; ok && len(vs) >= 1 {
|
||
kvs[key] = vs[len(vs)-1:]
|
||
}
|
||
|
||
case "RETAIN_FIRST":
|
||
fallthrough
|
||
default:
|
||
if vs, ok := kvs[key]; ok && len(vs) >= 1 {
|
||
kvs[key] = vs[:1]
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// only for body
|
||
func (h jsonHandler) handle(host, path string, oriData []byte, mapSourceData map[string]MapSourceData) (data []byte, err error) {
|
||
// arbitary order. for example: remove → rename → replace → add → append → map → dedupe
|
||
if !gjson.ValidBytes(oriData) {
|
||
return nil, errors.New("invalid json body")
|
||
}
|
||
data = oriData
|
||
|
||
for _, kvtOp := range h.kvtOps {
|
||
switch kvtOp.kvtOpType {
|
||
case RemoveK:
|
||
// remove
|
||
for _, remove := range kvtOp.removeKvtGroup {
|
||
if data, err = sjson.DeleteBytes(data, remove.key); err != nil {
|
||
return nil, errors.Wrap(err, errRemove.Error())
|
||
}
|
||
}
|
||
case RenameK:
|
||
// rename: 若指定 oldKey 不存在则无操作;否则将 oldKey 的值追加给 newKey,并删除 oldKey:value
|
||
for _, rename := range kvtOp.renameKvtGroup {
|
||
oldKey, newKey := rename.oldKey, rename.newKey
|
||
value := gjson.GetBytes(data, oldKey)
|
||
if !value.Exists() {
|
||
continue
|
||
}
|
||
if data, err = sjson.SetBytes(data, newKey, value.Value()); err != nil {
|
||
return nil, errors.Wrap(err, errRename.Error())
|
||
}
|
||
if data, err = sjson.DeleteBytes(data, oldKey); err != nil {
|
||
return nil, errors.Wrap(err, errRename.Error())
|
||
}
|
||
}
|
||
case ReplaceK:
|
||
// replace: 若指定 key 不存在,则相当于添加操作;否则替换 value 为 newValue
|
||
for _, replace := range kvtOp.replaceKvtGroup {
|
||
key, newValue, valueType := replace.key, replace.newValue, replace.typ
|
||
if valueType == "string" && replace.reg != nil {
|
||
newValue = replace.reg.matchAndReplace(newValue, host, path)
|
||
}
|
||
convertedNewValue, err := convertByJsonType(valueType, newValue)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, errReplace.Error())
|
||
}
|
||
if data, err = sjson.SetBytes(data, key, convertedNewValue); err != nil {
|
||
return nil, errors.Wrap(err, errReplace.Error())
|
||
}
|
||
}
|
||
case AddK:
|
||
// add: 若指定 key 存在则无操作;否则添加 key:value
|
||
for _, add := range kvtOp.addKvtGroup {
|
||
key, value, valueType := add.key, add.value, add.typ
|
||
if gjson.GetBytes(data, key).Exists() {
|
||
continue
|
||
}
|
||
if valueType == "string" && add.reg != nil {
|
||
value = add.reg.matchAndReplace(value, host, path)
|
||
}
|
||
convertedValue, err := convertByJsonType(valueType, value)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, errAdd.Error())
|
||
}
|
||
if data, err = sjson.SetBytes(data, key, convertedValue); err != nil {
|
||
return nil, errors.Wrap(err, errAdd.Error())
|
||
}
|
||
}
|
||
case AppendK:
|
||
// append: 若指定 key 存在,则追加同名 kv;否则相当于添加操作
|
||
// 当原本的 value 为数组时,追加;当原本的 value 不为数组时,将原本的 value 和 appendValue 组成数组
|
||
for _, append_ := range kvtOp.appendKvtGroup {
|
||
key, appendValue, valueType := append_.key, append_.appendValue, append_.typ
|
||
if valueType == "string" && append_.reg != nil {
|
||
appendValue = append_.reg.matchAndReplace(appendValue, host, path)
|
||
}
|
||
convertedAppendValue, err := convertByJsonType(valueType, appendValue)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, errAppend.Error())
|
||
}
|
||
oldValue := gjson.GetBytes(data, key)
|
||
if !oldValue.Exists() {
|
||
if data, err = sjson.SetBytes(data, key, convertedAppendValue); err != nil { // key: appendValue
|
||
return nil, errors.Wrap(err, errAppend.Error())
|
||
}
|
||
continue
|
||
}
|
||
|
||
// oldValue exists
|
||
if oldValue.IsArray() {
|
||
if len(oldValue.Array()) == 0 {
|
||
if data, err = sjson.SetBytes(data, key, []interface{}{convertedAppendValue}); err != nil { // key: [appendValue]
|
||
return nil, errors.Wrap(err, errAppend.Error())
|
||
}
|
||
continue
|
||
}
|
||
|
||
// len(oldValue.Array()) != 0
|
||
oldValues := make([]interface{}, 0, len(oldValue.Array())+1)
|
||
for _, val := range oldValue.Array() {
|
||
oldValues = append(oldValues, val.Value())
|
||
}
|
||
if data, err = sjson.SetBytes(data, key, append(oldValues, convertedAppendValue)); err != nil { // key: [oldValue..., appendValue]
|
||
return nil, errors.Wrap(err, errAppend.Error())
|
||
}
|
||
continue
|
||
}
|
||
|
||
// oldValue is not array
|
||
if data, err = sjson.SetBytes(data, key, []interface{}{oldValue.Value(), convertedAppendValue}); err != nil { // key: [oldValue, appendValue]
|
||
return nil, errors.Wrap(err, errAppend.Error())
|
||
}
|
||
}
|
||
case MapK:
|
||
// map: 若指定 fromKey 不存在则无操作;否则将 fromKey 的值映射给 toKey 的值
|
||
for _, map_ := range kvtOp.mapKvtGroup {
|
||
fromKey, toKey := map_.fromKey, map_.toKey
|
||
if kvtOp.mapSource == "headers" {
|
||
fromKey = strings.ToLower(fromKey)
|
||
}
|
||
source, exist := mapSourceData[kvtOp.mapSource]
|
||
if !exist {
|
||
proxywasm.LogWarnf("map key failed, source:%s not exists", kvtOp.mapSource)
|
||
continue
|
||
}
|
||
|
||
proxywasm.LogDebugf("search key:%s in source:%s", fromKey, kvtOp.mapSource)
|
||
if fromValue, ok := source.search(fromKey); ok {
|
||
switch source.mapSourceType {
|
||
case "headers", "querys", "bodyKv":
|
||
if data, err = sjson.SetBytes(data, toKey, fromValue); err != nil {
|
||
return nil, errors.Wrap(err, errMap.Error())
|
||
}
|
||
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
|
||
case "bodyJson":
|
||
if valueJson, ok := fromValue.(gjson.Result); ok {
|
||
if data, err = sjson.SetBytes(data, toKey, valueJson.Value()); err != nil {
|
||
return nil, errors.Wrap(err, errMap.Error())
|
||
}
|
||
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
case DedupeK:
|
||
// dedupe: 根据 strategy 去重:RETAIN_UNIQUE 保留所有唯一值,RETAIN_LAST 保留最后一个值,RETAIN_FIRST 保留第一个值 (default)
|
||
for _, dedupe := range kvtOp.dedupeKvtGroup {
|
||
key, strategy := dedupe.key, dedupe.strategy
|
||
value := gjson.GetBytes(data, key)
|
||
if !value.Exists() || !value.IsArray() {
|
||
continue
|
||
}
|
||
|
||
// value is array
|
||
values := value.Array()
|
||
if len(values) == 0 {
|
||
continue
|
||
}
|
||
|
||
var dedupedVal interface{}
|
||
switch strings.ToUpper(strategy) {
|
||
case "RETAIN_UNIQUE":
|
||
uniSet, uniques := make(map[string]struct{}), make([]interface{}, 0)
|
||
for _, v := range values {
|
||
vstr := v.String()
|
||
if _, ok := uniSet[vstr]; !ok {
|
||
uniSet[vstr] = struct{}{}
|
||
uniques = append(uniques, v.Value())
|
||
}
|
||
}
|
||
if len(uniques) == 1 {
|
||
dedupedVal = uniques[0] // key: uniques[0]
|
||
} else if len(uniques) > 1 {
|
||
dedupedVal = uniques // key: [uniques...]
|
||
}
|
||
|
||
case "RETAIN_LAST":
|
||
dedupedVal = values[len(values)-1].Value() // key: last
|
||
|
||
case "RETAIN_FIRST":
|
||
fallthrough
|
||
default:
|
||
dedupedVal = values[0].Value() // key: first
|
||
}
|
||
|
||
if dedupedVal == nil {
|
||
continue
|
||
}
|
||
if data, err = sjson.SetBytes(data, key, dedupedVal); err != nil {
|
||
return nil, errors.Wrap(err, errDedupe.Error())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return data, nil
|
||
}
|
||
|
||
type removeKvt struct {
|
||
key string
|
||
}
|
||
type renameKvt struct {
|
||
oldKey string
|
||
newKey string
|
||
typ string
|
||
}
|
||
type replaceKvt struct {
|
||
key string
|
||
newValue string
|
||
typ string
|
||
*reg
|
||
}
|
||
type addKvt struct {
|
||
key string
|
||
value string
|
||
typ string
|
||
*reg
|
||
}
|
||
type appendKvt struct {
|
||
key string
|
||
appendValue string
|
||
typ string
|
||
*reg
|
||
}
|
||
|
||
type mapKvt struct {
|
||
fromKey string
|
||
toKey string
|
||
}
|
||
type dedupeKvt struct {
|
||
key string
|
||
strategy string
|
||
}
|
||
type KvtOpType int
|
||
|
||
const (
|
||
RemoveK KvtOpType = iota
|
||
RenameK
|
||
ReplaceK
|
||
AddK
|
||
AppendK
|
||
MapK
|
||
DedupeK
|
||
)
|
||
|
||
type kvtOperation struct {
|
||
kvtOpType KvtOpType
|
||
removeKvtGroup []removeKvt
|
||
renameKvtGroup []renameKvt
|
||
replaceKvtGroup []replaceKvt
|
||
addKvtGroup []addKvt
|
||
appendKvtGroup []appendKvt
|
||
mapKvtGroup []mapKvt
|
||
dedupeKvtGroup []dedupeKvt
|
||
mapSource string
|
||
}
|
||
|
||
func newKvtGroup(rules []TransformRule, typ string) (g []kvtOperation, isChange bool, withMapKvt bool, err error) {
|
||
g = []kvtOperation{}
|
||
for _, r := range rules {
|
||
var prams []Param
|
||
switch typ {
|
||
case "headers":
|
||
prams = r.headers
|
||
case "querys":
|
||
prams = r.querys
|
||
case "body":
|
||
prams = r.body
|
||
}
|
||
|
||
var kvtOp kvtOperation
|
||
switch r.operate {
|
||
case "remove":
|
||
kvtOp.kvtOpType = RemoveK
|
||
case "rename":
|
||
kvtOp.kvtOpType = RenameK
|
||
case "map":
|
||
kvtOp.kvtOpType = MapK
|
||
case "replace":
|
||
kvtOp.kvtOpType = ReplaceK
|
||
case "dedupe":
|
||
kvtOp.kvtOpType = DedupeK
|
||
case "add":
|
||
kvtOp.kvtOpType = AddK
|
||
case "append":
|
||
kvtOp.kvtOpType = AppendK
|
||
default:
|
||
return nil, false, false, errors.Wrap(err, "invalid operation type")
|
||
}
|
||
for _, p := range prams {
|
||
switch r.operate {
|
||
case "remove":
|
||
key := p.removeParam.key
|
||
if typ == "headers" {
|
||
key = strings.ToLower(key)
|
||
}
|
||
kvtOp.removeKvtGroup = append(kvtOp.removeKvtGroup, removeKvt{key})
|
||
case "rename":
|
||
if typ == "headers" {
|
||
p.renameParam.oldKey = strings.ToLower(p.renameParam.oldKey)
|
||
p.renameParam.newKey = strings.ToLower(p.renameParam.newKey)
|
||
}
|
||
kvtOp.renameKvtGroup = append(kvtOp.renameKvtGroup, renameKvt{p.renameParam.oldKey, p.renameParam.newKey, p.valueType})
|
||
case "map":
|
||
if typ == "headers" {
|
||
p.mapParam.toKey = strings.ToLower(p.mapParam.toKey)
|
||
}
|
||
kvtOp.mapSource = r.mapSource
|
||
if kvtOp.mapSource == "self" {
|
||
kvtOp.mapSource = typ
|
||
r.mapSource = typ
|
||
}
|
||
if kvtOp.mapSource == "headers" {
|
||
p.mapParam.fromKey = strings.ToLower(p.mapParam.fromKey)
|
||
}
|
||
|
||
kvtOp.mapKvtGroup = append(kvtOp.mapKvtGroup, mapKvt{p.mapParam.fromKey, p.mapParam.toKey})
|
||
case "dedupe":
|
||
if typ == "headers" {
|
||
p.dedupeParam.key = strings.ToLower(p.dedupeParam.key)
|
||
}
|
||
kvtOp.dedupeKvtGroup = append(kvtOp.dedupeKvtGroup, dedupeKvt{p.dedupeParam.key, p.dedupeParam.strategy})
|
||
case "replace":
|
||
if typ == "headers" {
|
||
p.replaceParam.key = strings.ToLower(p.replaceParam.key)
|
||
}
|
||
var rg *reg
|
||
if p.hostPattern != "" || p.pathPattern != "" {
|
||
rg, err = newReg(p.hostPattern, p.pathPattern)
|
||
if err != nil {
|
||
return nil, false, false, errors.Wrap(err, "failed to new reg")
|
||
}
|
||
}
|
||
kvtOp.replaceKvtGroup = append(kvtOp.replaceKvtGroup, replaceKvt{p.replaceParam.key, p.replaceParam.newValue, p.valueType, rg})
|
||
case "add":
|
||
if typ == "headers" {
|
||
p.addParam.key = strings.ToLower(p.addParam.key)
|
||
}
|
||
var rg *reg
|
||
if p.hostPattern != "" || p.pathPattern != "" {
|
||
rg, err = newReg(p.hostPattern, p.pathPattern)
|
||
if err != nil {
|
||
return nil, false, false, errors.Wrap(err, "failed to new reg")
|
||
}
|
||
}
|
||
kvtOp.addKvtGroup = append(kvtOp.addKvtGroup, addKvt{p.addParam.key, p.addParam.value, p.valueType, rg})
|
||
case "append":
|
||
if typ == "headers" {
|
||
p.appendParam.key = strings.ToLower(p.appendParam.key)
|
||
}
|
||
var rg *reg
|
||
if p.hostPattern != "" || p.pathPattern != "" {
|
||
rg, err = newReg(p.hostPattern, p.pathPattern)
|
||
if err != nil {
|
||
return nil, false, false, errors.Wrap(err, "failed to new reg")
|
||
}
|
||
}
|
||
kvtOp.appendKvtGroup = append(kvtOp.appendKvtGroup, appendKvt{p.appendParam.key, p.appendParam.appendValue, p.valueType, rg})
|
||
}
|
||
}
|
||
isChange = isChange || len(kvtOp.removeKvtGroup) != 0 ||
|
||
len(kvtOp.renameKvtGroup) != 0 || len(kvtOp.replaceKvtGroup) != 0 ||
|
||
len(kvtOp.addKvtGroup) != 0 || len(kvtOp.appendKvtGroup) != 0 ||
|
||
len(kvtOp.mapKvtGroup) != 0 || len(kvtOp.dedupeKvtGroup) != 0
|
||
withMapKvt = withMapKvt || len(kvtOp.mapKvtGroup) != 0
|
||
g = append(g, kvtOp)
|
||
}
|
||
|
||
return g, isChange, withMapKvt, nil
|
||
}
|
||
|
||
type MapSourceData struct {
|
||
mapSourceType string
|
||
kvs map[string][]string // headers or querys or body in kvs
|
||
json []byte // body in json
|
||
}
|
||
|
||
func (msdata MapSourceData) search(fromKey string) (interface{}, bool) {
|
||
switch msdata.mapSourceType {
|
||
case "headers", "querys", "bodyKv":
|
||
fromValue, ok := msdata.kvs[fromKey]
|
||
return fromValue, ok
|
||
case "bodyJson":
|
||
fromValue := gjson.GetBytes(msdata.json, fromKey)
|
||
if !fromValue.Exists() {
|
||
return nil, false
|
||
}
|
||
return fromValue, true
|
||
default:
|
||
return "", false
|
||
}
|
||
}
|
||
|
||
func bodyMapSourceInRule(rules []TransformRule) bool {
|
||
for _, r := range rules {
|
||
if r.operate == "map" && r.mapSource == "body" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
type kvtReg struct {
|
||
kvt
|
||
*reg
|
||
}
|
||
|
||
type kvt struct {
|
||
key string
|
||
value string
|
||
typ string
|
||
}
|
||
|
||
type reg struct {
|
||
hostReg *regexp.Regexp
|
||
pathReg *regexp.Regexp
|
||
}
|
||
|
||
// you can only choose one between host and path
|
||
func newReg(hostPatten, pathPatten string) (r *reg, err error) {
|
||
r = ®{}
|
||
if hostPatten != "" {
|
||
r.hostReg, err = regexp.Compile(hostPatten)
|
||
return
|
||
}
|
||
if pathPatten != "" {
|
||
r.pathReg, err = regexp.Compile(pathPatten)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
func (r reg) matchAndReplace(value, host, path string) string {
|
||
if r.hostReg != nil && r.hostReg.MatchString(host) {
|
||
return r.hostReg.ReplaceAllString(host, value)
|
||
}
|
||
if r.pathReg != nil && r.pathReg.MatchString(path) {
|
||
return r.pathReg.ReplaceAllString(path, value)
|
||
}
|
||
return value
|
||
}
|