Skip to content

Commit

Permalink
[implement#56] retrieve values from uri for dubbo
Browse files Browse the repository at this point in the history
  • Loading branch information
williamfeng323 committed Nov 17, 2020
1 parent b2e0f27 commit a678ce7
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 55 deletions.
31 changes: 26 additions & 5 deletions pkg/client/dubbo/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package dubbo

import (
"bytes"
"encoding/json"
"io/ioutil"
"net/url"
"reflect"
Expand All @@ -31,6 +30,7 @@ import (
)

import (
"encoding/json"
"github.com/dubbogo/dubbo-go-proxy/pkg/client"
"github.com/dubbogo/dubbo-go-proxy/pkg/common/constant"
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
Expand All @@ -40,6 +40,7 @@ var mappers = map[string]client.ParamMapper{
constant.QueryStrings: queryStringsMapper{},
constant.Headers: headerMapper{},
constant.RequestBody: bodyMapper{},
constant.RequestURI: uriMapper{},
}

type queryStringsMapper struct{}
Expand Down Expand Up @@ -109,21 +110,41 @@ 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
}
json.Unmarshal(rawBody, &mapBody)
val, err := client.GetMapValue(mapBody, keys)

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

type uriMapper struct{}

func (um uriMapper) Map(mp config.MappingParam, c client.Request, target interface{}) 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 {
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) {
Expand Down
33 changes: 33 additions & 0 deletions pkg/client/dubbo/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,39 @@ func TestBodyMapper(t *testing.T) {
assert.Equal(t, target[2], map[string]interface{}(map[string]interface{}{"firstName": "Joe", "lastName": "Biden"}))
}

func TestURIMapper(t *testing.T) {
r, _ := http.NewRequest("POST", "/mock/12345/joe&age=19", bytes.NewReader([]byte(`{"sex": "male", "name":{"firstName": "Joe", "lastName": "Biden"}}`)))
r.Header.Set("Auth", "1234567")
api := mock.GetMockAPI(config.MethodGet, "/mock/:id/:name")
api.IntegrationRequest.MappingParams = []config.MappingParam{
{
Name: "requestBody.sex",
MapTo: "0",
},
{
Name: "requestBody.name.lastName",
MapTo: "1",
},
{
Name: "uri.name",
MapTo: "2",
},
{
Name: "uri.id",
MapTo: "3",
},
}
um := uriMapper{}
target := []interface{}{}
req := client.NewReq(context.TODO(), r, api)
err := um.Map(api.IntegrationRequest.MappingParams[3], *req, &target)
assert.Nil(t, err)
err = um.Map(api.IntegrationRequest.MappingParams[2], *req, &target)
assert.Nil(t, err)
assert.Equal(t, target[2], "joe")
assert.Equal(t, target[3], "12345")
}

func TestValidateTarget(t *testing.T) {
target := []interface{}{}
val, err := validateTarget(&target)
Expand Down
7 changes: 2 additions & 5 deletions pkg/client/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ 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 (
"github.com/dubbogo/dubbo-go-proxy/pkg/client"
"github.com/pkg/errors"
)

// RestMetadata dubbo metadata, api config
Expand Down Expand Up @@ -122,10 +122,7 @@ func (dc *Client) MapParams(req *client.Request) (reqData interface{}, err error
mp := req.API.IntegrationRequest.MappingParams
r := newRequestParams()
if len(mp) == 0 {
r.Body, err = req.IngressRequest.GetBody()
if err != nil {
return nil, errors.New("Retrieve request body failed")
}
r.Body = req.IngressRequest.Body
r.Header = req.IngressRequest.Header.Clone()
queryValues, err := url.ParseQuery(req.IngressRequest.URL.RawQuery)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions pkg/client/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ func TestMapParams(t *testing.T) {
}
api.IntegrationRequest.HTTPBackendConfig.Protocol = "https"
api.IntegrationRequest.HTTPBackendConfig.TargetURL = "localhost"
r, _ = http.NewRequest("POST", "/mock/test?team=theBoys", bytes.NewReader([]byte("{\"id\":\"12345\",\"age\":\"19\",\"testStruct\":{\"name\":\"mock\",\"test\":\"happy\",\"nickName\":\"trump\"}}")))
r.Header.Set("Auth", "12345")
req = client.NewReq(context.TODO(), r, api)
val, err = hClient.MapParams(req)
assert.Nil(t, err)
Expand Down
17 changes: 10 additions & 7 deletions pkg/client/http/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,19 @@ func (bm bodyMapper) Map(mp config.MappingParam, c client.Request, rawTarget int
return err
}

body, err := c.IngressRequest.GetBody()
if err != nil {
return err
}
rawBody, err := ioutil.ReadAll(body)
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, fromKey)

if err != nil {
return errors.Wrapf(err, "Error when get body value from key %s", fromKey)
}
setTarget(target, to, strings.Join(toKey, constant.Dot), val)
return nil
}
Expand Down Expand Up @@ -166,6 +167,9 @@ func setTarget(target *requestParams, to string, key string, val interface{}) er
target.Query.Set(key, val.(string))
case constant.RequestBody:
rawBody, err := ioutil.ReadAll(target.Body)
defer func() {
target.Body = ioutil.NopCloser(bytes.NewReader(rawBody))
}()
if err != nil {
return errors.New("Raw body parse failed")
}
Expand All @@ -177,7 +181,6 @@ func setTarget(target *requestParams, to string, key string, val interface{}) er
if err != nil {
return errors.New("Stringify map to body failed")
}
target.Body = ioutil.NopCloser(bytes.NewReader(rawBody))
default:
return errors.Errorf("Mapping target to %s does not support", to)
}
Expand Down
10 changes: 7 additions & 3 deletions pkg/client/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type ParamMapper interface {
// ParseMapSource parses the source parameter config in the mappingParams
// the source parameter in config could be queryStrings.*, headers.*, requestBody.*
func ParseMapSource(source string) (from string, params []string, err error) {
reg := regexp.MustCompile(`^([queryStrings|headers|requestBody][\w|\d]+)\.([\w|\d|\.|\-]+)$`)
reg := regexp.MustCompile(`^([uri|queryStrings|headers|requestBody][\w|\d]+)\.([\w|\d|\.|\-]+)$`)
if !reg.MatchString(source) {
return "", nil, errors.New("Parameter mapping config incorrect. Please fix it")
}
Expand All @@ -57,8 +57,12 @@ func GetMapValue(sourceMap map[string]interface{}, keys []string) (interface{},
if ok && len(keys) == 1 {
return rvalue.Interface(), nil
}
if rvalue.Type().Kind() != reflect.Map {
if rvalue.Type().Kind() != reflect.Map && len(keys) == 1 {
return rvalue.Interface(), nil
}
return GetMapValue(sourceMap[keys[0]].(map[string]interface{}), keys[1:])
deeperStruct, ok := sourceMap[keys[0]].(map[string]interface{})
if !ok {
return nil, errors.Errorf("%s is not a map structure. It contains %v", keys[0], sourceMap[keys[0]])
}
return GetMapValue(deeperStruct, keys[1:])
}
7 changes: 7 additions & 0 deletions pkg/client/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,11 @@ func TestGetMapValue(t *testing.T) {
val, err = GetMapValue(testMap, []string{"structure", "name"})
assert.Nil(t, err)
assert.Equal(t, val, "joe")
val, err = GetMapValue(map[string]interface{}{}, []string{"structure"})
assert.Nil(t, val)
assert.EqualError(t, err, "structure does not exist in request body")

val, err = GetMapValue(map[string]interface{}{"structure": "test"}, []string{"structure", "name"})
assert.Nil(t, val)
assert.EqualError(t, err, "structure is not a map structure. It contains test")
}
3 changes: 3 additions & 0 deletions pkg/common/constant/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const (
QueryStrings = "queryStrings"
// Headers name of api config mapping from/to
Headers = "headers"
// RequestURI name of api config mapping from/to, retrieve parameters from uri
// for instance, https://test.com/:id uri.id will retrieve the :id parameter
RequestURI = "uri"
// Dot defines the . which will be used to present the path to specific field in the body
Dot = "."
)
11 changes: 11 additions & 0 deletions pkg/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@

package router

import (
"net/url"
)

import (
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
"strings"
)

// API describes the minimum configuration of an RESTful api configure in gateway
type API struct {
URLPattern string `json:"urlPattern" yaml:"urlPattern"`
config.Method `json:"method,inline" yaml:"method,inline"`
}

// GetURIParams returns the values retrieved from the rawURL
func (api *API) GetURIParams(rawURL url.URL) url.Values {
sourceURL := strings.Split(rawURL.Path, "&")[0]
return wildcardMatch(api.URLPattern, sourceURL)
}
50 changes: 50 additions & 0 deletions pkg/router/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 router

import (
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
"github.com/stretchr/testify/assert"
"net/url"
"testing"
)

func TestGetURIParams(t *testing.T) {
api := API{
URLPattern: "/mock/:id/:name",
Method: getMockMethod(config.MethodGet),
}
u, _ := url.Parse("https://test.com/mock/12345/Joe")
values := api.GetURIParams(*u)
assert.Equal(t, values.Get("id"), "12345")
assert.Equal(t, values.Get("name"), "Joe")

u, _ = url.Parse("https://test.com/Mock/12345/Joe")
values = api.GetURIParams(*u)
assert.Equal(t, values.Get("id"), "12345")
assert.Equal(t, values.Get("name"), "Joe")

u, _ = url.Parse("https://test.com/mock/12345")
values = api.GetURIParams(*u)
assert.Nil(t, values)

api.URLPattern = "/mock/test"
u, _ = url.Parse("https://test.com/mock/12345/Joe?status=up")
values = api.GetURIParams(*u)
assert.Nil(t, values)
}
39 changes: 15 additions & 24 deletions pkg/router/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
import (
"github.com/dubbogo/dubbo-go-proxy/pkg/common/constant"
"github.com/dubbogo/dubbo-go-proxy/pkg/config"
"net/url"
)

// Node defines the single method of the router configured API
Expand Down Expand Up @@ -105,16 +106,6 @@ func (rt *Route) FindAPI(fullPath string, httpverb config.HTTPVerb) (*API, bool)
return nil, false
}

// UpdateResource updates the resource configuration
func (rt *Route) UpdateResource() error {
return nil
}

// PutResource creates the resource into the tree
func (rt *Route) PutResource() error {
return nil
}

func (rt *Route) findNode(fullPath string) (*Node, bool) {
lowerPath := strings.ToLower(fullPath)
var n interface{}
Expand All @@ -134,7 +125,7 @@ func (rt *Route) searchWildcard(fullPath string) (*Node, bool) {
defer rt.lock.RUnlock()
wildcardPaths := rt.wildcardTree.Keys()
for _, p := range wildcardPaths {
if wildcardMatch(p.(string), fullPath) {
if wildcardMatch(p.(string), fullPath) != nil {
n, ok := rt.wildcardTree.Get(p)
return n.(*Node), ok
}
Expand All @@ -145,22 +136,22 @@ func (rt *Route) searchWildcard(fullPath string) (*Node, bool) {
// wildcardMatch validate if the checkPath meets the wildcardPath,
// for example /vought/12345 should match wildcard path /vought/:id;
// /vought/1234abcd/status should not match /vought/:id;
func wildcardMatch(wildcardPath string, checkPath string) bool {
lowerWildcardPath := strings.ToLower(wildcardPath)
lowerCheckPath := strings.ToLower(checkPath)
wPathSplit := strings.Split(strings.TrimPrefix(lowerWildcardPath, constant.PathSlash), constant.PathSlash)
cPathSplit := strings.Split(strings.TrimPrefix(lowerCheckPath, constant.PathSlash), constant.PathSlash)
if len(wPathSplit) != len(cPathSplit) {
return false
func wildcardMatch(wildcardPath string, checkPath string) url.Values {
cPaths := strings.Split(strings.TrimLeft(checkPath, constant.PathSlash), constant.PathSlash)
wPaths := strings.Split(strings.TrimLeft(wildcardPath, constant.PathSlash), constant.PathSlash)
result := url.Values{}
if len(cPaths) == 0 || len(wPaths) == 0 || len(cPaths) != len(wPaths) {
return nil
}
for i, s := range wPathSplit {
if strings.Contains(s, constant.PathParamIdentifier) {
cPathSplit[i] = s
} else if wPathSplit[i] != cPathSplit[i] {
return false
for i := 0; i < len(cPaths); i++ {
if strings.ToLower(cPaths[i]) != strings.ToLower(wPaths[i]) && !strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) {
return nil
}
if strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) {
result.Add(strings.TrimPrefix(wPaths[i], constant.PathParamIdentifier), cPaths[i])
}
}
return strings.Join(wPathSplit, constant.PathSlash) == strings.Join(cPathSplit, constant.PathSlash)
return result
}

// NewRoute returns an empty router tree
Expand Down
Loading

0 comments on commit a678ce7

Please sign in to comment.