blob: efbf4f4ba59b47b0ed3dfec9f6e45f515a477019 [file] [log] [blame]
/*
* 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]
}