Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/http 2 http param mapping #57

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“s” is lost

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")
}
98 changes: 72 additions & 26 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,25 @@ import (
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
)

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

type queryStringsMapper struct{}

// nolint
func (qm queryStringsMapper) Map(mp config.MappingParam, c client.Request, target interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't the caller pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originally, I don't hope to modify anything inside the client.Request, but since I have to add back the body after mapping the request body, I think pointer might be better now.

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,18 +63,19 @@ 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
}

type headerMapper struct{}

// nolint
func (hm headerMapper) Map(mp config.MappingParam, c client.Request, target interface{}) error {
rv, err := validateTarget(target)
if err != nil {
Expand All @@ -83,7 +96,9 @@ func (hm headerMapper) Map(mp config.MappingParam, c client.Request, target inte

type bodyMapper struct{}

// nolint
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 @@ -98,36 +113,67 @@ func (bm bodyMapper) Map(mp config.MappingParam, c client.Request, target interf
}

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{}{}
err = json.Unmarshal(rawBody, &mapBody)
if err != nil {
return err
}

val, err := getMapValue(mapBody, keys)
json.Unmarshal(rawBody, &mapBody)
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
type uriMapper struct{}

// nolint
func (um uriMapper) Map(mp config.MappingParam, c client.Request, target interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map is public, add // nolint?

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 {
return errors.Errorf("Parameter mapping %v incorrect", mp)
}
uriValues := c.API.GetURIParams(*c.IngressRequest.URL)
setTarget(rv, pos, uriValues.Get(keys[0]))
return 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))
}
Loading