| /* |
| * 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 http |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "io" |
| "io/ioutil" |
| "net/http" |
| "net/url" |
| "reflect" |
| "strings" |
| ) |
| |
| import ( |
| "github.com/dubbogo/dubbo-go-pixiu-filter/pkg/api/config" |
| "github.com/pkg/errors" |
| ) |
| |
| 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 requestParams struct { |
| Header http.Header |
| Query url.Values |
| Body io.ReadCloser |
| URIParams url.Values |
| } |
| |
| func newRequestParams() *requestParams { |
| return &requestParams{ |
| Header: http.Header{}, |
| Query: url.Values{}, |
| Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), |
| URIParams: url.Values{}, |
| } |
| } |
| |
| type queryStringsMapper struct{} |
| |
| // nolint |
| func (qs queryStringsMapper) Map(mp config.MappingParam, c *client.Request, rawTarget interface{}, option client.RequestOption) error { |
| target, fromKey, to, toKey, err := mapPrepare(mp, rawTarget) |
| 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") |
| } |
| rawValue := queryValues.Get(fromKey[0]) |
| if len(rawValue) == 0 { |
| return errors.Errorf("%s in query parameters not found", fromKey[0]) |
| } |
| setTarget(target, to, toKey[0], rawValue) |
| return nil |
| } |
| |
| type headerMapper struct{} |
| |
| // nolint |
| func (hm headerMapper) Map(mp config.MappingParam, c *client.Request, rawTarget interface{}, option client.RequestOption) error { |
| target, fromKey, to, toKey, err := mapPrepare(mp, rawTarget) |
| if err != nil { |
| return err |
| } |
| |
| rawHeader := c.IngressRequest.Header.Get(fromKey[0]) |
| if len(rawHeader) == 0 { |
| return errors.Errorf("Header %s not found", fromKey[0]) |
| } |
| setTarget(target, to, toKey[0], rawHeader) |
| return nil |
| } |
| |
| type bodyMapper struct{} |
| |
| // nolint |
| func (bm bodyMapper) Map(mp config.MappingParam, c *client.Request, rawTarget interface{}, option client.RequestOption) error { |
| // TO-DO: add support for content-type other than application/json |
| target, fromKey, to, toKey, err := mapPrepare(mp, rawTarget) |
| if err != nil { |
| return err |
| } |
| |
| 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 |
| } |
| |
| type uriMapper struct{} |
| |
| // nolint |
| func (um uriMapper) Map(mp config.MappingParam, c *client.Request, rawTarget interface{}, option client.RequestOption) error { |
| target, fromKey, to, toKey, err := mapPrepare(mp, rawTarget) |
| if err != nil { |
| return err |
| } |
| if to == constant.RequestURI { |
| } |
| uriValues := router.GetURIParams(&c.API, *c.IngressRequest.URL) |
| if uriValues == nil { |
| return errors.New("No URI parameters found") |
| } |
| setTarget(target, to, strings.Join(toKey, constant.Dot), uriValues.Get(fromKey[0])) |
| return nil |
| } |
| |
| func validateTarget(target interface{}) (*requestParams, error) { |
| val, ok := target.(*requestParams) |
| if !ok || val == nil { |
| return nil, errors.New("Target params must be a requestParams pointer") |
| } |
| return val, nil |
| } |
| |
| func setTarget(target *requestParams, to string, key string, val interface{}) error { |
| valType := reflect.TypeOf(val) |
| if (to == constant.Headers || to == constant.QueryStrings) && valType.Kind() != reflect.String { |
| return errors.Errorf("%s only accepts string", to) |
| } |
| switch to { |
| case constant.Headers: |
| target.Header.Set(key, val.(string)) |
| case constant.RequestURI: |
| target.URIParams.Set(key, val.(string)) |
| case constant.QueryStrings: |
| 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 read failed") |
| } |
| mapBody := map[string]interface{}{} |
| if len(rawBody) != 0 { |
| err = json.Unmarshal(rawBody, &mapBody) |
| if err != nil { |
| return errors.New("Raw body parse failed") |
| } |
| } |
| setMapWithPath(mapBody, key, val) |
| rawBody, err = json.Marshal(mapBody) |
| if err != nil { |
| return errors.New("Stringify map to body failed") |
| } |
| default: |
| return errors.Errorf("Mapping target to %s does not support", to) |
| } |
| return nil |
| } |
| |
| // setMapWithPath set the value to the target map. If the origin targetMap has |
| // {"abc": "cde": {"f":1, "g":2}} and the path is abc, value is "123", then the |
| // targetMap will be updated to {"abc", "123"} |
| func setMapWithPath(targetMap map[string]interface{}, path string, val interface{}) map[string]interface{} { |
| keys := strings.Split(path, constant.Dot) |
| |
| _, ok := targetMap[keys[0]] |
| if len(keys) == 1 { |
| targetMap[keys[0]] = val |
| return targetMap |
| } |
| if !ok && len(keys) != 1 { |
| targetMap[keys[0]] = make(map[string]interface{}) |
| targetMap[keys[0]] = setMapWithPath(targetMap[keys[0]].(map[string]interface{}), strings.Join(keys[1:], constant.Dot), val) |
| } |
| return targetMap |
| } |
| |
| func mapPrepare(mp config.MappingParam, rawTarget interface{}) (target *requestParams, fromKey []string, to string, toKey []string, err error) { |
| // ensure the target is a pointer and type is requestParams |
| target, err = validateTarget(rawTarget) |
| if err != nil { |
| return nil, nil, "", nil, err |
| } |
| // retrieve the mapping values' origin param name |
| _, fromKey, err = client.ParseMapSource(mp.Name) |
| if err != nil { |
| return nil, nil, "", nil, err |
| } |
| // retrieve the mapping values' target param name and param types(header/uri/query/request body) |
| to, toKey, err = client.ParseMapSource(mp.MapTo) |
| if err != nil { |
| return nil, nil, "", nil, err |
| } |
| return target, fromKey, to, toKey, nil |
| } |