| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 dubbo |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "io/ioutil" |
| "net/url" |
| "reflect" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| import ( |
| "github.com/dubbogo/dubbo-go-pixiu-filter/pkg/api/config" |
| |
| "github.com/pkg/errors" |
| |
| "github.com/spf13/cast" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/client" |
| "github.com/apache/dubbo-go-pixiu/pkg/common/constant" |
| "github.com/apache/dubbo-go-pixiu/pkg/router" |
| ) |
| |
| var mappers = map[string]client.ParamMapper{ |
| constant.QueryStrings: queryStringsMapper{}, |
| constant.Headers: headerMapper{}, |
| constant.RequestBody: bodyMapper{}, |
| constant.RequestURI: uriMapper{}, |
| } |
| |
| type dubboTarget struct { |
| Values []interface{} // the slice contains the parameters. |
| Types []string // the slice contains the parameters' types. It should match the values one by one. |
| } |
| |
| // pre-allocate proper memory according to the params' usability. |
| func newDubboTarget(mps []config.MappingParam) *dubboTarget { |
| length := 0 |
| |
| for i := 0; i < len(mps); i++ { |
| isGeneric, v := getGenericMapTo(mps[i].MapTo) |
| if isGeneric && v != optionKeyValues { |
| continue |
| } |
| length++ |
| } |
| |
| if length > 0 { |
| val := make([]interface{}, length) |
| target := &dubboTarget{ |
| Values: val, |
| Types: make([]string, length), |
| } |
| return target |
| } |
| return nil |
| } |
| |
| type queryStringsMapper struct{} |
| |
| // nolint |
| func (qm queryStringsMapper) Map(mp config.MappingParam, c *client.Request, target interface{}, option client.RequestOption) error { |
| t, err := validateTarget(target) |
| if err != nil { |
| return err |
| } |
| queryValues, err := url.ParseQuery(c.IngressRequest.URL.RawQuery) |
| if err != nil { |
| return errors.Wrap(err, "Error happened when parsing the query paramters") |
| } |
| _, key, err := client.ParseMapSource(mp.Name) |
| if err != nil { |
| return err |
| } |
| pos, err := strconv.Atoi(mp.MapTo) |
| if err != nil && option == nil { |
| return errors.Errorf("Parameter mapping %v incorrect", mp) |
| } |
| qValue := queryValues.Get(key[0]) |
| if len(qValue) == 0 { |
| return errors.Errorf("Query parameter %s does not exist", key) |
| } |
| |
| return setTargetWithOpt(c, option, t, pos, qValue, mp.MapType) |
| } |
| |
| type headerMapper struct{} |
| |
| // nolint |
| func (hm headerMapper) Map(mp config.MappingParam, c *client.Request, target interface{}, option client.RequestOption) error { |
| rv, err := validateTarget(target) |
| if err != nil { |
| return err |
| } |
| _, key, err := client.ParseMapSource(mp.Name) |
| pos, err := strconv.Atoi(mp.MapTo) |
| if err != nil && option == nil { |
| return errors.Errorf("Parameter mapping %+v incorrect", mp) |
| } |
| header := c.IngressRequest.Header.Get(key[0]) |
| if len(header) == 0 { |
| return errors.Errorf("Header %s not found", key[0]) |
| } |
| |
| return setTargetWithOpt(c, option, rv, pos, header, mp.MapType) |
| } |
| |
| type bodyMapper struct{} |
| |
| // nolint |
| func (bm bodyMapper) Map(mp config.MappingParam, c *client.Request, target interface{}, option client.RequestOption) error { |
| // TO-DO: add support for content-type other than application/json |
| rv, err := validateTarget(target) |
| if err != nil { |
| return err |
| } |
| _, keys, err := client.ParseMapSource(mp.Name) |
| if err != nil { |
| return err |
| } |
| pos, err := strconv.Atoi(mp.MapTo) |
| if err != nil && option == nil { |
| return errors.Errorf("Parameter mapping %v incorrect, parameters for Dubbo backend must be mapped to an int to represent position", mp) |
| } |
| |
| rawBody, err := ioutil.ReadAll(c.IngressRequest.Body) |
| defer func() { |
| c.IngressRequest.Body = ioutil.NopCloser(bytes.NewReader(rawBody)) |
| }() |
| if err != nil { |
| return err |
| } |
| mapBody := map[string]interface{}{} |
| json.Unmarshal(rawBody, &mapBody) |
| val, err := client.GetMapValue(mapBody, keys) |
| |
| if err := setTargetWithOpt(c, option, rv, pos, val, mp.MapType); err != nil { |
| return errors.Wrap(err, "set target fail") |
| } |
| |
| c.IngressRequest.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody)) |
| return nil |
| } |
| |
| type uriMapper struct{} |
| |
| // nolint |
| func (um uriMapper) Map(mp config.MappingParam, c *client.Request, target interface{}, option client.RequestOption) error { |
| rv, err := validateTarget(target) |
| if err != nil { |
| return err |
| } |
| _, keys, err := client.ParseMapSource(mp.Name) |
| if err != nil { |
| return err |
| } |
| pos, err := strconv.Atoi(mp.MapTo) |
| if err != nil && option == nil { |
| return errors.Errorf("Parameter mapping %v incorrect", mp) |
| } |
| uriValues := router.GetURIParams(&c.API, *c.IngressRequest.URL) |
| |
| return setTargetWithOpt(c, option, rv, pos, uriValues.Get(keys[0]), mp.MapType) |
| } |
| |
| // validateTarget verify if the incoming target for the Map function |
| // can be processed as expected. |
| func validateTarget(target interface{}) (*dubboTarget, error) { |
| val, ok := target.(*dubboTarget) |
| if !ok { |
| return nil, errors.New("Target params for dubbo backend must be *dubbogoTarget") |
| } |
| return val, nil |
| } |
| |
| func setTargetWithOpt(req *client.Request, option client.RequestOption, |
| target *dubboTarget, pos int, value interface{}, targetType string) error { |
| if option != nil { |
| return setGenericTarget(req, option, target, value, targetType) |
| } |
| value, err := mapTypes(targetType, value) |
| if err != nil { |
| return err |
| } |
| setCommonTarget(target, pos, value, targetType) |
| return nil |
| } |
| |
| func setGenericTarget(req *client.Request, option client.RequestOption, |
| target *dubboTarget, value interface{}, targetType string) error { |
| var err error |
| switch option.(type) { |
| case *groupOpt, *versionOpt, *interfaceOpt, *applicationOpt, *methodOpt: |
| err = option.Action(req, value) |
| case *valuesOpt: |
| err = option.Action(target, [2]interface{}{value, targetType}) |
| case *paramTypesOpt: |
| err = option.Action(target, value) |
| } |
| return err |
| } |
| |
| func setCommonTarget(target *dubboTarget, pos int, value interface{}, targetType string) { |
| // if the mapTo position is greater than the numbers of usable parameters, |
| // extend the values and types slices. It changes the address of the the target. |
| if cap(target.Values) <= pos { |
| list := make([]interface{}, pos+1-len(target.Values)) |
| typeList := make([]string, pos+1-len(target.Types)) |
| target.Values = append(target.Values, list...) |
| target.Types = append(target.Types, typeList...) |
| } |
| target.Values[pos] = value |
| target.Types[pos] = targetType |
| } |
| |
| func mapTypes(jType string, originVal interface{}) (interface{}, error) { |
| targetType, ok := constant.JTypeMapper[jType] |
| if !ok { |
| return nil, errors.Errorf("Invalid parameter type: %s", jType) |
| } |
| switch targetType { |
| case reflect.TypeOf(""): |
| return cast.ToStringE(originVal) |
| case reflect.TypeOf(int(0)): |
| return cast.ToIntE(originVal) |
| case reflect.TypeOf(int8(0)): |
| return cast.ToInt8E(originVal) |
| case reflect.TypeOf(int16(16)): |
| return cast.ToInt16E(originVal) |
| case reflect.TypeOf(int32(0)): |
| return cast.ToInt32E(originVal) |
| case reflect.TypeOf(int64(0)): |
| return cast.ToInt64E(originVal) |
| case reflect.TypeOf(float32(0)): |
| return cast.ToFloat32E(originVal) |
| case reflect.TypeOf(float64(0)): |
| return cast.ToFloat64E(originVal) |
| case reflect.TypeOf(true): |
| return cast.ToBoolE(originVal) |
| case reflect.TypeOf(time.Time{}): |
| return cast.ToTimeE(originVal) |
| default: |
| return originVal, nil |
| } |
| } |
| |
| // getGenericMapTo will parse the mapTo field, if the mapTo value is |
| // opt.xxx, the "opt." prefix will identify the param mapTo generic field, |
| // supporting generic fields: interface, group, application, method, version, |
| // values, types |
| func getGenericMapTo(mapTo string) (isGeneric bool, genericField string) { |
| fields := strings.Split(mapTo, ".") |
| if len(fields) != 2 || fields[0] != "opt" { |
| return false, "" |
| } |
| if _, ok := DefaultMapOption[fields[1]]; !ok { |
| return false, "" |
| } |
| return true, fields[1] |
| } |