Skip to content

Commit

Permalink
[implement-#54] add paramerter mapping for http-2-http
Browse files Browse the repository at this point in the history
Former-commit-id: b2e0f27
  • Loading branch information
williamfeng323 committed Nov 17, 2020
1 parent e780789 commit f8e0b59
Show file tree
Hide file tree
Showing 12 changed files with 747 additions and 121 deletions.
5 changes: 3 additions & 2 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@

package client

// Client represents the interface of http/dubbo clients
type Client interface {
Init() error
Close() error
Call(req *Request) (resp Response, err error)

// MappingParams mapping param, uri, query, body ...
MappingParams(req *Request) (types []string, reqData []interface{}, err error)
// MapParams mapping param, uri, query, body ...
MapParams(req *Request) (reqData interface{}, err error)
}
49 changes: 7 additions & 42 deletions pkg/client/dubbo/dubbo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package dubbo

import (
"context"
"reflect"
"strings"
"sync"
"time"
Expand All @@ -29,7 +28,6 @@ import (
"github.com/apache/dubbo-go/common/constant"
dg "github.com/apache/dubbo-go/config"
"github.com/apache/dubbo-go/protocol/dubbo"
"github.com/pkg/errors"
)

import (
Expand All @@ -44,12 +42,6 @@ const (
JavaLangClassName = "java.lang.Long"
)

var mappers = map[string]client.ParamMapper{
"queryStrings": queryStringsMapper{},
"headers": headerMapper{},
"requestBody": bodyMapper{},
}

var (
dubboClient *Client
onceClient = sync.Once{}
Expand Down Expand Up @@ -140,11 +132,11 @@ func (dc *Client) Close() error {
// Call invoke service
func (dc *Client) Call(req *client.Request) (resp client.Response, err error) {
dm := req.API.Method.IntegrationRequest
types, values, err := dc.MappingParams(req)
types := req.API.IntegrationRequest.ParamTypes
values, err := dc.MapParams(req)
if err != nil {
return *client.EmptyResponse, err
}

method := dm.Method
logger.Debugf("[dubbo-go-proxy] invoke, method:%s, types:%s, reqData:%v", method, types, values)

Expand All @@ -165,22 +157,22 @@ func (dc *Client) Call(req *client.Request) (resp client.Response, err error) {
return *NewDubboResponse(rst), nil
}

// MappingParams param mapping to api.
func (dc *Client) MappingParams(req *client.Request) ([]string, []interface{}, error) {
// MapParams param mapping to api.
func (dc *Client) MapParams(req *client.Request) (interface{}, error) {
r := req.API.Method.IntegrationRequest
var values []interface{}
for _, mappingParam := range r.MappingParams {
source, _, err := client.ParseMapSource(mappingParam.Name)
if err != nil {
return nil, nil, err
return nil, err
}
if mapper, ok := mappers[source]; ok {
if err := mapper.Map(mappingParam, *req, &values); err != nil {
return nil, nil, err
return nil, err
}
}
}
return req.API.IntegrationRequest.ParamTypes, values, nil
return values, nil
}

func (dc *Client) get(key string) *dg.GenericService {
Expand Down Expand Up @@ -246,30 +238,3 @@ func (dc *Client) create(key string, irequest config.IntegrationRequest) *dg.Gen
dc.GenericServicePool[key] = clientService
return clientService
}

func validateTarget(target interface{}) (reflect.Value, error) {
rv := reflect.ValueOf(target)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return rv, errors.New("Target params must be a non-nil pointer")
}
if _, ok := target.(*[]interface{}); !ok {
return rv, errors.New("Target params for dubbo backend must be *[]interface{}")
}
return rv, nil
}

func setTarget(rv reflect.Value, pos int, value interface{}) {
if rv.Kind() != reflect.Ptr && rv.Type().Name() != "" && rv.CanAddr() {
rv = rv.Addr()
} else {
rv = rv.Elem()
}

tempValue := rv.Interface().([]interface{})
if len(tempValue) <= pos {
list := make([]interface{}, pos+1-len(tempValue))
tempValue = append(tempValue, list...)
}
tempValue[pos] = value
rv.Set(reflect.ValueOf(tempValue))
}
61 changes: 13 additions & 48 deletions pkg/client/dubbo/dubbo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ func TestMappingParams(t *testing.T) {
},
}
req := client.NewReq(context.TODO(), r, api)
_, params, err := dClient.MappingParams(req)
params, err := dClient.MapParams(req)
assert.Nil(t, err)
assert.Equal(t, params[0], "12345")
assert.Equal(t, params[1], "19")
assert.Equal(t, params.([]interface{})[0], "12345")
assert.Equal(t, params.([]interface{})[1], "19")

r, _ = http.NewRequest("GET", "/mock/test?id=12345&age=19", bytes.NewReader([]byte("")))
api = mock.GetMockAPI(config.MethodGet, "/mock/test")
Expand All @@ -127,11 +127,11 @@ func TestMappingParams(t *testing.T) {
}
r.Header.Set("Auth", "1234567")
req = client.NewReq(context.TODO(), r, api)
_, params, err = dClient.MappingParams(req)
params, err = dClient.MapParams(req)
assert.Nil(t, err)
assert.Equal(t, params[0], "12345")
assert.Equal(t, params[1], "19")
assert.Equal(t, params[2], "1234567")
assert.Equal(t, params.([]interface{})[0], "12345")
assert.Equal(t, params.([]interface{})[1], "19")
assert.Equal(t, params.([]interface{})[2], "1234567")

r, _ = http.NewRequest("POST", "/mock/test?id=12345&age=19", bytes.NewReader([]byte(`{"sex": "male", "name":{"firstName": "Joe", "lastName": "Biden"}}`)))
api = mock.GetMockAPI(config.MethodGet, "/mock/test")
Expand Down Expand Up @@ -159,46 +159,11 @@ func TestMappingParams(t *testing.T) {
}
r.Header.Set("Auth", "1234567")
req = client.NewReq(context.TODO(), r, api)
_, params, err = dClient.MappingParams(req)
params, err = dClient.MapParams(req)
assert.Nil(t, err)
assert.Equal(t, params[0], "12345")
assert.Equal(t, params[1], "19")
assert.Equal(t, params[2], "1234567")
assert.Equal(t, params[3], "male")
assert.Equal(t, params[4], "Joe")
}

func TestValidateTarget(t *testing.T) {
target := []interface{}{}
val, err := validateTarget(&target)
assert.Nil(t, err)
assert.NotNil(t, val)
_, err = validateTarget(target)
assert.EqualError(t, err, "Target params must be a non-nil pointer")
target2 := ""
_, err = validateTarget(&target2)
assert.EqualError(t, err, "Target params for dubbo backend must be *[]interface{}")
}

func TestParseMapSource(t *testing.T) {
from, key, err := client.ParseMapSource("queryStrings.id")
assert.Nil(t, err)
assert.Equal(t, from, "queryStrings")
assert.Equal(t, key[0], "id")

from, key, err = client.ParseMapSource("headers.id")
assert.Nil(t, err)
assert.Equal(t, from, "headers")
assert.Equal(t, key[0], "id")

from, key, err = client.ParseMapSource("requestBody.user.id")
assert.Nil(t, err)
assert.Equal(t, from, "requestBody")
assert.Equal(t, key[0], "user")
assert.Equal(t, key[1], "id")

from, key, err = client.ParseMapSource("what.user.id")
assert.EqualError(t, err, "Parameter mapping config incorrect. Please fix it")
from, key, err = client.ParseMapSource("requestBody.*userid")
assert.EqualError(t, err, "Parameter mapping config incorrect. Please fix it")
assert.Equal(t, params.([]interface{})[0], "12345")
assert.Equal(t, params.([]interface{})[1], "19")
assert.Equal(t, params.([]interface{})[2], "1234567")
assert.Equal(t, params.([]interface{})[3], "male")
assert.Equal(t, params.([]interface{})[4], "Joe")
}
65 changes: 43 additions & 22 deletions pkg/client/dubbo/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"bytes"
"encoding/json"
"io/ioutil"
"net/url"
"reflect"
"strconv"
)
Expand All @@ -35,14 +36,23 @@ import (
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
)

var mappers = map[string]client.ParamMapper{
constant.QueryStrings: queryStringsMapper{},
constant.Headers: headerMapper{},
constant.RequestBody: bodyMapper{},
}

type queryStringsMapper struct{}

func (qm queryStringsMapper) Map(mp config.MappingParam, c client.Request, target interface{}) error {
rv, err := validateTarget(target)
if err != nil {
return err
}
c.IngressRequest.ParseForm()
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
Expand All @@ -51,12 +61,12 @@ func (qm queryStringsMapper) Map(mp config.MappingParam, c client.Request, targe
if err != nil {
return errors.Errorf("Parameter mapping %v incorrect", mp)
}
formValue := c.IngressRequest.Form.Get(key[0])
if len(formValue) == 0 {
qValue := queryValues.Get(key[0])
if len(qValue) == 0 {
return errors.Errorf("Query parameter %s does not exist", key)
}

setTarget(rv, pos, formValue)
setTarget(rv, pos, qValue)

return nil
}
Expand Down Expand Up @@ -84,6 +94,7 @@ func (hm headerMapper) Map(mp config.MappingParam, c client.Request, target inte
type bodyMapper struct{}

func (bm bodyMapper) Map(mp config.MappingParam, c client.Request, target interface{}) error {
// TO-DO: add support for content-type other than application/json
rv, err := validateTarget(target)
if err != nil {
return err
Expand All @@ -106,28 +117,38 @@ func (bm bodyMapper) Map(mp config.MappingParam, c client.Request, target interf
if err != nil {
return err
}

val, err := getMapValue(mapBody, keys)
val, err := client.GetMapValue(mapBody, keys)

setTarget(rv, pos, val)
c.IngressRequest.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))
return nil
}

func getMapValue(sourceMap map[string]interface{}, keys []string) (interface{}, error) {
if len(keys) == 1 && keys[0] == constant.DefaultBodyAll {
return sourceMap, nil
}
for i, key := range keys {
_, ok := sourceMap[key]
if !ok {
return nil, errors.Errorf("%s does not exist in request body", key)
}
rvalue := reflect.ValueOf(sourceMap[key])
if rvalue.Type().Kind() != reflect.Map {
return rvalue.Interface(), nil
}
return getMapValue(sourceMap[key].(map[string]interface{}), keys[i+1:])
}
return nil, nil
// validateTarget verify if the incoming target for the Map function
// can be processed as expected.
func validateTarget(target interface{}) (reflect.Value, error) {
rv := reflect.ValueOf(target)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return rv, errors.New("Target params must be a non-nil pointer")
}
if _, ok := target.(*[]interface{}); !ok {
return rv, errors.New("Target params for dubbo backend must be *[]interface{}")
}
return rv, nil
}

func setTarget(rv reflect.Value, pos int, value interface{}) {
if rv.Kind() != reflect.Ptr && rv.Type().Name() != "" && rv.CanAddr() {
rv = rv.Addr()
} else {
rv = rv.Elem()
}

tempValue := rv.Interface().([]interface{})
if len(tempValue) <= pos {
list := make([]interface{}, pos+1-len(tempValue))
tempValue = append(tempValue, list...)
}
tempValue[pos] = value
rv.Set(reflect.ValueOf(tempValue))
}
20 changes: 20 additions & 0 deletions pkg/client/dubbo/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ func TestBodyMapper(t *testing.T) {
Name: "requestBody.name.lastName",
MapTo: "1",
},
{
Name: "requestBody.name",
MapTo: "2",
},
}
bm := bodyMapper{}
target := []interface{}{}
Expand All @@ -134,4 +138,20 @@ func TestBodyMapper(t *testing.T) {
err = bm.Map(api.IntegrationRequest.MappingParams[1], *req, &target)
assert.Nil(t, err)
assert.Equal(t, target[1], "Biden")

err = bm.Map(api.IntegrationRequest.MappingParams[2], *req, &target)
assert.Nil(t, err)
assert.Equal(t, target[2], map[string]interface{}(map[string]interface{}{"firstName": "Joe", "lastName": "Biden"}))
}

func TestValidateTarget(t *testing.T) {
target := []interface{}{}
val, err := validateTarget(&target)
assert.Nil(t, err)
assert.NotNil(t, val)
_, err = validateTarget(target)
assert.EqualError(t, err, "Target params must be a non-nil pointer")
target2 := ""
_, err = validateTarget(&target2)
assert.EqualError(t, err, "Target params for dubbo backend must be *[]interface{}")
}
Loading

0 comments on commit f8e0b59

Please sign in to comment.