From 787e5f71f69a9661e5228f07ca85a7f4f317b3fe Mon Sep 17 00:00:00 2001 From: william feng <> Date: Thu, 10 Sep 2020 14:51:55 +0800 Subject: [PATCH 01/18] define new api config file --- .gitignore | 3 +- configs/api_config.yaml | 57 ++++++++++++++++++++++++++--- pkg/config/api_config.go | 67 +++++++++++++++++++++++++++++++++++ pkg/config/api_config_test.go | 0 4 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 pkg/config/api_config.go create mode 100644 pkg/config/api_config_test.go diff --git a/.gitignore b/.gitignore index f7a989311..9a8941e54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ ./dubbo-go-proxy /logs -/.idea \ No newline at end of file +/.idea +/.vscode \ No newline at end of file diff --git a/configs/api_config.yaml b/configs/api_config.yaml index b5e8a8749..9723bf7cb 100644 --- a/configs/api_config.yaml +++ b/configs/api_config.yaml @@ -1,5 +1,52 @@ -- name: "test-dubbo/user" - target: "queryUser" - method: "POST" - types: - - "com.ikurento.user.User" +# - name: "test-dubbo/user" +# target: "queryUser" +# method: "POST" +# types: +# - "com.ikurento.user.User" + +name: api name +description: api description +resources: + - path: '/' + description: resource documentation + filters: + - filterA + - filterB + methods: + - method: GET + inboundRequest: + type: http + headers: + - name: auth + - required: true + queryStrings: + - name: page + required: false + - name: type + required: false + requestBody: + - modelDefinition + integrationRequest: + type: dubbo + paramTypes: + - "com.ikurento.user.User" + + +Definitions: + - name: modelDefinition + contentType: application/json + schema: >- + { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer" + }, + "type" : { + "type" : "string" + }, + "price" : { + "type" : "number" + } + } + } diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go new file mode 100644 index 000000000..e982966da --- /dev/null +++ b/pkg/config/api_config.go @@ -0,0 +1,67 @@ +package model + +import ( + "sync" +) + +var ( + CacheApi = sync.Map{} +) + +// Api is api gateway concept, control request from browser、Mobile APP、third party people +type Api struct { + Name string `json:"name"` + ITypeStr string `json:"itype"` + IType ApiType `json:"-"` + OTypeStr string `json:"otype"` + OType ApiType `json:"-"` + Status Status `json:"status"` + Metadata interface{} `json:"metadata"` + Method string `json:"method"` + RequestMethod +} + +var EmptyApi = &Api{} + +func NewApi() *Api { + return &Api{} +} + +func (a *Api) FindApi(name string) (*Api, bool) { + if v, ok := CacheApi.Load(name); ok { + return v.(*Api), true + } + + return nil, false +} + +func (a *Api) MatchMethod(method string) bool { + i := RequestMethodValue[method] + if a.RequestMethod == RequestMethod(i) { + return true + } + + return false +} + +func (a *Api) IsOk(name string) bool { + if v, ok := CacheApi.Load(name); ok { + return v.(*Api).Status == Up + } + + return false +} + +// Offline api offline +func (a *Api) Offline(name string) { + if v, ok := CacheApi.Load(name); ok { + v.(*Api).Status = Down + } +} + +// Online api online +func (a *Api) Online(name string) { + if v, ok := CacheApi.Load(name); ok { + v.(*Api).Status = Up + } +} diff --git a/pkg/config/api_config_test.go b/pkg/config/api_config_test.go new file mode 100644 index 000000000..e69de29bb From d333538e74de18e274d8cfc073c7853ff34f918c Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 12 Sep 2020 16:57:40 +0800 Subject: [PATCH 02/18] changed the api config structure --- configs/api_config.yaml | 12 +- go.sum | 2 + pkg/common/yaml/testdata/config.yml | 7 + pkg/common/yaml/yaml.go | 60 +++++++++ pkg/common/yaml/yaml_test.go | 70 ++++++++++ pkg/config/api_config.go | 197 +++++++++++++++++++++------- pkg/config/api_config_test.go | 24 ++++ pkg/config/mock/api_config.yml | 52 ++++++++ pkg/logger/logger.go | 2 +- 9 files changed, 379 insertions(+), 47 deletions(-) create mode 100644 pkg/common/yaml/testdata/config.yml create mode 100644 pkg/common/yaml/yaml.go create mode 100644 pkg/common/yaml/yaml_test.go create mode 100644 pkg/config/mock/api_config.yml diff --git a/configs/api_config.yaml b/configs/api_config.yaml index 9723bf7cb..f041446e1 100644 --- a/configs/api_config.yaml +++ b/configs/api_config.yaml @@ -8,12 +8,14 @@ name: api name description: api description resources: - path: '/' + type: restful description: resource documentation filters: - filterA - filterB methods: - - method: GET + - httpVerb: GET + onAir: true inboundRequest: type: http headers: @@ -28,8 +30,14 @@ resources: - modelDefinition integrationRequest: type: dubbo + applicationName: BDTService + group: test + version: 1.0.0 + interface: com.ikurento.user.UserProvider + Method: GetUser paramTypes: - - "com.ikurento.user.User" + - java.lang.String + ClusterName: test_dubbo Definitions: diff --git a/go.sum b/go.sum index 19b2130bf..cc1e82215 100644 --- a/go.sum +++ b/go.sum @@ -693,6 +693,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/common/yaml/testdata/config.yml b/pkg/common/yaml/testdata/config.yml new file mode 100644 index 000000000..b5c2ca8ad --- /dev/null +++ b/pkg/common/yaml/testdata/config.yml @@ -0,0 +1,7 @@ + +intTest: 11 +booleanTest: false +strTest: "strTest" + +child: + strTest: "childStrTest" \ No newline at end of file diff --git a/pkg/common/yaml/yaml.go b/pkg/common/yaml/yaml.go new file mode 100644 index 000000000..de67bfca5 --- /dev/null +++ b/pkg/common/yaml/yaml.go @@ -0,0 +1,60 @@ +/* + * 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 yaml + +import ( + "io/ioutil" + "path" +) + +import ( + perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +// LoadYMLConfig Load yml config byte from file +func LoadYMLConfig(confProFile string) ([]byte, error) { + if len(confProFile) == 0 { + return nil, perrors.Errorf("configure file name is nil") + } + + if path.Ext(confProFile) != ".yml" { + return nil, perrors.Errorf("configure file name{%v} suffix must be .yml", confProFile) + } + + return ioutil.ReadFile(confProFile) +} + +// UnmarshalYMLConfig Load yml config byte from file, then unmarshal to object +func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { + confFileStream, err := LoadYMLConfig(confProFile) + if err != nil { + return confFileStream, perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) + } + return confFileStream, yaml.Unmarshal(confFileStream, out) +} + +//UnmarshalYML unmarshals decodes the first document found within the in byte slice and assigns decoded values into the out value. +func UnmarshalYML(data []byte, out interface{}) error { + return yaml.Unmarshal(data, out) +} + +// MarshalYML serializes the value provided into a YAML document. +func MarshalYML(in interface{}) ([]byte, error) { + return yaml.Marshal(in) +} diff --git a/pkg/common/yaml/yaml_test.go b/pkg/common/yaml/yaml_test.go new file mode 100644 index 000000000..5a271a258 --- /dev/null +++ b/pkg/common/yaml/yaml_test.go @@ -0,0 +1,70 @@ +/* + * 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 yaml + +import ( + "path/filepath" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestUnmarshalYMLConfig(t *testing.T) { + conPath, err := filepath.Abs("./testdata/config.yml") + assert.NoError(t, err) + c := &Config{} + _, err = UnmarshalYMLConfig(conPath, c) + assert.NoError(t, err) + assert.Equal(t, "strTest", c.StrTest) + assert.Equal(t, 11, c.IntTest) + assert.Equal(t, false, c.BooleanTest) + assert.Equal(t, "childStrTest", c.ChildConfig.StrTest) +} + +func TestUnmarshalYMLConfigError(t *testing.T) { + c := &Config{} + _, err := UnmarshalYMLConfig("./testdata/config", c) + assert.Error(t, err) + _, err = UnmarshalYMLConfig("", c) + assert.Error(t, err) +} + +func TestUnmarshalYML(t *testing.T) { + c := &Config{} + b, err := LoadYMLConfig("./testdata/config.yml") + assert.NoError(t, err) + err = UnmarshalYML(b, c) + assert.NoError(t, err) + assert.Equal(t, "strTest", c.StrTest) + assert.Equal(t, 11, c.IntTest) + assert.Equal(t, false, c.BooleanTest) + assert.Equal(t, "childStrTest", c.ChildConfig.StrTest) +} + +type Config struct { + StrTest string `yaml:"strTest" default:"default" json:"strTest,omitempty" property:"strTest"` + IntTest int `default:"109" yaml:"intTest" json:"intTest,omitempty" property:"intTest"` + BooleanTest bool `yaml:"booleanTest" default:"true" json:"booleanTest,omitempty"` + ChildConfig ChildConfig `yaml:"child" json:"child,omitempty"` +} + +type ChildConfig struct { + StrTest string `default:"strTest" default:"default" yaml:"strTest" json:"strTest,omitempty"` +} diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index e982966da..9e5bd74b1 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -1,67 +1,176 @@ -package model +package config import ( - "sync" + "log" ) -var ( - CacheApi = sync.Map{} +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/client/dubbo" + "github.com/dubbogo/dubbo-go-proxy/pkg/common/yaml" + perrors "github.com/pkg/errors" ) -// Api is api gateway concept, control request from browser、Mobile APP、third party people -type Api struct { - Name string `json:"name"` - ITypeStr string `json:"itype"` - IType ApiType `json:"-"` - OTypeStr string `json:"otype"` - OType ApiType `json:"-"` - Status Status `json:"status"` - Metadata interface{} `json:"metadata"` - Method string `json:"method"` - RequestMethod -} +// import ( +// "sync" +// ) + +// var ( +// CacheApi = sync.Map{} +// ) + +// // Api is api gateway concept, control request from browser、Mobile APP、third party people +// type Api struct { +// Name string `json:"name"` +// ITypeStr string `json:"itype"` +// IType ApiType `json:"-"` +// OTypeStr string `json:"otype"` +// OType ApiType `json:"-"` +// Status Status `json:"status"` +// Metadata interface{} `json:"metadata"` +// Method string `json:"method"` +// RequestMethod +// } + +// var EmptyApi = &Api{} + +// func NewApi() *Api { +// return &Api{} +// } + +// func (a *Api) FindApi(name string) (*Api, bool) { +// if v, ok := CacheApi.Load(name); ok { +// return v.(*Api), true +// } + +// return nil, false +// } + +// func (a *Api) MatchMethod(method string) bool { +// i := RequestMethodValue[method] +// if a.RequestMethod == RequestMethod(i) { +// return true +// } + +// return false +// } + +// func (a *Api) IsOk(name string) bool { +// if v, ok := CacheApi.Load(name); ok { +// return v.(*Api).Status == Up +// } + +// return false +// } + +// // Offline api offline +// func (a *Api) Offline(name string) { +// if v, ok := CacheApi.Load(name); ok { +// v.(*Api).Status = Down +// } +// } -var EmptyApi = &Api{} +// // Online api online +// func (a *Api) Online(name string) { +// if v, ok := CacheApi.Load(name); ok { +// v.(*Api).Status = Up +// } +// } -func NewApi() *Api { - return &Api{} +// HTTPVerb defines the restful api http verb +type HTTPVerb string + +const ( + // MethodAny any method + MethodAny HTTPVerb = "ANY" + // MethodGet get + MethodGet HTTPVerb = "GET" + // MethodHead head + MethodHead HTTPVerb = "HEAD" + // MethodPost post + MethodPost HTTPVerb = "POST" + // MethodPut put + MethodPut HTTPVerb = "PUT" + // MethodPatch patch + MethodPatch HTTPVerb = "PATCH" // RFC 5789 + // MethodDelete delete + MethodDelete HTTPVerb = "DELETE" + // MethodOptions options + MethodOptions HTTPVerb = "OPTIONS" +) + +// APIConfig defines the data structure of the api gateway configuration +type APIConfig struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + Resources []Resource `yaml:"resources"` + Definitions []Definition `yaml:"definitions"` } -func (a *Api) FindApi(name string) (*Api, bool) { - if v, ok := CacheApi.Load(name); ok { - return v.(*Api), true - } +// Resource defines the API path +type Resource struct { + Type string `yaml:"type"` // Restful, Dubbo + Path string `yaml:"path"` + Description string `yaml:"description"` + Filters []string `yaml:"filters"` + Methods []Method `yaml:"methods"` + Resources []Resource `yaml:"resources,omitempty"` +} - return nil, false +// Method defines the method of the api +type Method struct { + OnAir bool `yaml:"onAir"` + HTTPVerb `yaml:"httpVerb"` + InboundRequest `yaml:"inboundRequest"` } -func (a *Api) MatchMethod(method string) bool { - i := RequestMethodValue[method] - if a.RequestMethod == RequestMethod(i) { - return true - } +// InboundRequest defines the details of the inbound +type InboundRequest struct { + Type string `yaml:"type"` //http, TO-DO: dubbo + Headers []Params `yaml:"headers"` + QueryStrings []Params `yaml:"queryStrings"` + RequestBody []BodyDefinition `yaml:"requestBody"` +} - return false +// Params defines the simple parameter definition +type Params struct { + Name string `yaml:"name"` + Required bool `yaml:"required"` } -func (a *Api) IsOk(name string) bool { - if v, ok := CacheApi.Load(name); ok { - return v.(*Api).Status == Up - } +// BodyDefinition connects the request body to the definitions +type BodyDefinition struct { + DefinitionName string `yaml:"definitionName"` + ContentType string `yaml:"contentType"` +} - return false +// IntegrationRequest defines the backend request format and target +type IntegrationRequest struct { + Type string `yaml:"type"` // dubbo, TO-DO: http + dubbo.DubboMetadata `yaml:"dubboMetaData,inline"` } -// Offline api offline -func (a *Api) Offline(name string) { - if v, ok := CacheApi.Load(name); ok { - v.(*Api).Status = Down - } +// MappingParams defines the mapping rules of headers and queryStrings +type MappingParams struct { + Name string `yaml:"name"` + MapTo string `yaml:"mapTo"` } -// Online api online -func (a *Api) Online(name string) { - if v, ok := CacheApi.Load(name); ok { - v.(*Api).Status = Up +// Definition defines the complex json request body +type Definition struct { + Name string `yaml:"name"` + Schema string `yaml:"schema"` // use json schema +} + +// LoadAPIConfigFromFile load the api config from file +func LoadAPIConfigFromFile(path string) (*APIConfig, error) { + if len(path) == 0 { + return nil, perrors.Errorf("Config file not specified") + } + log.Printf("Load API configuration file form %s", path) + apiConf := &APIConfig{} + _, err := yaml.UnmarshalYMLConfig(path, apiConf) + if err != nil { + return nil, perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } + return apiConf, nil } diff --git a/pkg/config/api_config_test.go b/pkg/config/api_config_test.go index e69de29bb..172b69172 100644 --- a/pkg/config/api_config_test.go +++ b/pkg/config/api_config_test.go @@ -0,0 +1,24 @@ +package config_test + +import ( + "log" + "testing" +) + +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" +) + +func TestLoadAPIConfigFromFile(t *testing.T) { + apiC, err := config.LoadAPIConfigFromFile("") + assert.Empty(t, apiC) + assert.EqualError(t, err, "Config file not specified") + + apiC, err = config.LoadAPIConfigFromFile("./mock/api_config.yml") + assert.Empty(t, err) + assert.Equal(t, apiC.Name, "api name") + bytes, _ := yaml.Marshal(apiC) + log.Printf("%s", bytes) +} diff --git a/pkg/config/mock/api_config.yml b/pkg/config/mock/api_config.yml new file mode 100644 index 000000000..624b90c9e --- /dev/null +++ b/pkg/config/mock/api_config.yml @@ -0,0 +1,52 @@ +name: api name +description: api description +resources: + - path: '/' + type: restful + description: resource documentation + filters: + - filterA + - filterB + methods: + - httpVerb: GET + onAir: true + inboundRequest: + type: http + headers: + - name: auth + required: true + queryStrings: + - name: page + required: false + - name: type + required: false + requestBody: + - definitionName: modelDefinition + contentType: application/json + integrationRequest: + type: dubbo + applicationName: BDTService + group: test + version: 1.0.0 + interface: com.ikurento.user.UserProvider + Method: GetUser + paramTypes: + - java.lang.String + ClusterName: test_dubbo +definitions: + - name: modelDefinition + schema: >- + { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer" + }, + "type" : { + "type" : "string" + }, + "price" : { + "type" : "number" + } + } + } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index bacbdfbad..71913a0f4 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -25,10 +25,10 @@ import ( ) import ( + "github.com/ghodss/yaml" perrors "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "gopkg.in/yaml.v2" ) var ( From 2b3fed881af0459c3488bffa56490c30f5574fd6 Mon Sep 17 00:00:00 2001 From: william feng <> Date: Sat, 19 Sep 2020 15:27:39 +0800 Subject: [PATCH 03/18] included the integration config for api --- configs/api_config.yaml | 21 +++---- go.mod | 2 +- go.sum | 4 +- pkg/client/dubbo/dubbo_client.go | 2 +- pkg/config/api_config.go | 101 +++++++++---------------------- pkg/config/api_config_test.go | 17 ++++++ pkg/config/mock/api_config.yml | 9 ++- 7 files changed, 61 insertions(+), 95 deletions(-) diff --git a/configs/api_config.yaml b/configs/api_config.yaml index f041446e1..35ab2d5bf 100644 --- a/configs/api_config.yaml +++ b/configs/api_config.yaml @@ -1,9 +1,3 @@ -# - name: "test-dubbo/user" -# target: "queryUser" -# method: "POST" -# types: -# - "com.ikurento.user.User" - name: api name description: api description resources: @@ -20,16 +14,20 @@ resources: type: http headers: - name: auth - - required: true + required: true queryStrings: - - name: page + - name: id required: false - name: type required: false requestBody: - - modelDefinition + - definitionName: modelDefinition + contentType: application/json integrationRequest: type: dubbo + mappingParams: + - name: queryStrings.id + mapTo: 1 applicationName: BDTService group: test version: 1.0.0 @@ -38,11 +36,8 @@ resources: paramTypes: - java.lang.String ClusterName: test_dubbo - - -Definitions: +definitions: - name: modelDefinition - contentType: application/json schema: >- { "type" : "object", diff --git a/go.mod b/go.mod index 2129b0bd4..c0782e824 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,4 @@ require ( github.com/urfave/cli v1.22.4 go.uber.org/zap v1.15.0 gopkg.in/yaml.v2 v2.2.8 -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index cc1e82215..4ffe74917 100644 --- a/go.sum +++ b/go.sum @@ -693,8 +693,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -715,4 +713,4 @@ k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= \ No newline at end of file diff --git a/pkg/client/dubbo/dubbo_client.go b/pkg/client/dubbo/dubbo_client.go index 225cef478..0d4a1bf98 100644 --- a/pkg/client/dubbo/dubbo_client.go +++ b/pkg/client/dubbo/dubbo_client.go @@ -49,7 +49,7 @@ type DubboMetadata struct { Version string `yaml:"version" json:"version" mapstructure:"version"` Interface string `yaml:"interface" json:"interface" mapstructure:"interface"` Method string `yaml:"method" json:"method" mapstructure:"method"` - Types []string `yaml:"type" json:"types" mapstructure:"types"` + Types []string `yaml:"types" json:"types" mapstructure:"types"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` ClusterName string `yaml:"cluster_name" json:"cluster_name,omitempty" property:"cluster_name"` ProtocolTypeStr string `yaml:"protocol_type" json:"protocol_type,omitempty" property:"protocol_type"` diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 9e5bd74b1..6544e225e 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -1,3 +1,20 @@ +/* + * 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 config import ( @@ -10,72 +27,6 @@ import ( perrors "github.com/pkg/errors" ) -// import ( -// "sync" -// ) - -// var ( -// CacheApi = sync.Map{} -// ) - -// // Api is api gateway concept, control request from browser、Mobile APP、third party people -// type Api struct { -// Name string `json:"name"` -// ITypeStr string `json:"itype"` -// IType ApiType `json:"-"` -// OTypeStr string `json:"otype"` -// OType ApiType `json:"-"` -// Status Status `json:"status"` -// Metadata interface{} `json:"metadata"` -// Method string `json:"method"` -// RequestMethod -// } - -// var EmptyApi = &Api{} - -// func NewApi() *Api { -// return &Api{} -// } - -// func (a *Api) FindApi(name string) (*Api, bool) { -// if v, ok := CacheApi.Load(name); ok { -// return v.(*Api), true -// } - -// return nil, false -// } - -// func (a *Api) MatchMethod(method string) bool { -// i := RequestMethodValue[method] -// if a.RequestMethod == RequestMethod(i) { -// return true -// } - -// return false -// } - -// func (a *Api) IsOk(name string) bool { -// if v, ok := CacheApi.Load(name); ok { -// return v.(*Api).Status == Up -// } - -// return false -// } - -// // Offline api offline -// func (a *Api) Offline(name string) { -// if v, ok := CacheApi.Load(name); ok { -// v.(*Api).Status = Down -// } -// } - -// // Online api online -// func (a *Api) Online(name string) { -// if v, ok := CacheApi.Load(name); ok { -// v.(*Api).Status = Up -// } -// } - // HTTPVerb defines the restful api http verb type HTTPVerb string @@ -118,14 +69,15 @@ type Resource struct { // Method defines the method of the api type Method struct { - OnAir bool `yaml:"onAir"` - HTTPVerb `yaml:"httpVerb"` - InboundRequest `yaml:"inboundRequest"` + OnAir bool `yaml:"onAir"` + HTTPVerb `yaml:"httpVerb"` + InboundRequest `yaml:"inboundRequest"` + IntegrationRequest `yaml:"integrationRequest"` } // InboundRequest defines the details of the inbound type InboundRequest struct { - Type string `yaml:"type"` //http, TO-DO: dubbo + RequestType string `yaml:"requestType"` //http, TO-DO: dubbo Headers []Params `yaml:"headers"` QueryStrings []Params `yaml:"queryStrings"` RequestBody []BodyDefinition `yaml:"requestBody"` @@ -145,12 +97,13 @@ type BodyDefinition struct { // IntegrationRequest defines the backend request format and target type IntegrationRequest struct { - Type string `yaml:"type"` // dubbo, TO-DO: http - dubbo.DubboMetadata `yaml:"dubboMetaData,inline"` + RequestType string `yaml:"requestType"` // dubbo, TO-DO: http + MappingParams []MappingParam `yaml:"mappingParams,omitempty"` + dubbo.DubboMetadata `yaml:"dubboMetaData,inline,omitempty"` } -// MappingParams defines the mapping rules of headers and queryStrings -type MappingParams struct { +// MappingParam defines the mapping rules of headers and queryStrings +type MappingParam struct { Name string `yaml:"name"` MapTo string `yaml:"mapTo"` } diff --git a/pkg/config/api_config_test.go b/pkg/config/api_config_test.go index 172b69172..0644ea9a3 100644 --- a/pkg/config/api_config_test.go +++ b/pkg/config/api_config_test.go @@ -1,3 +1,20 @@ +/* + * 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 config_test import ( diff --git a/pkg/config/mock/api_config.yml b/pkg/config/mock/api_config.yml index 624b90c9e..80769333e 100644 --- a/pkg/config/mock/api_config.yml +++ b/pkg/config/mock/api_config.yml @@ -11,12 +11,12 @@ resources: - httpVerb: GET onAir: true inboundRequest: - type: http + requestType: http headers: - name: auth required: true queryStrings: - - name: page + - name: id required: false - name: type required: false @@ -24,7 +24,10 @@ resources: - definitionName: modelDefinition contentType: application/json integrationRequest: - type: dubbo + requestType: dubbo + mappingParams: + - name: queryStrings.id + mapTo: 1 applicationName: BDTService group: test version: 1.0.0 From 9024094fc582a11d01e1714683c608a8199a029e Mon Sep 17 00:00:00 2001 From: "yangqing.xyq" Date: Mon, 21 Sep 2020 11:18:06 +0800 Subject: [PATCH 04/18] remove .idea --- .idea/.gitignore | 2 -- .idea/dubbo-go-proxy.iml | 9 --------- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 5 files changed, 31 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/dubbo-go-proxy.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index e7e9d11d4..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Default ignored files -/workspace.xml diff --git a/.idea/dubbo-go-proxy.iml b/.idea/dubbo-go-proxy.iml deleted file mode 100644 index 5e764c4f0..000000000 --- a/.idea/dubbo-go-proxy.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 28a804d89..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 288f721bd..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From a0dda1f9c0ef24156555ef6486a85f4aa8258e1d Mon Sep 17 00:00:00 2001 From: william feng <> Date: Mon, 21 Sep 2020 14:12:38 +0800 Subject: [PATCH 05/18] update the import to meet coding standards --- pkg/config/api_config.go | 6 +++--- pkg/config/api_config_test.go | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 6544e225e..bfc02f301 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -18,13 +18,13 @@ package config import ( - "log" + perrors "github.com/pkg/errors" ) import ( "github.com/dubbogo/dubbo-go-proxy/pkg/client/dubbo" "github.com/dubbogo/dubbo-go-proxy/pkg/common/yaml" - perrors "github.com/pkg/errors" + "github.com/dubbogo/dubbo-go-proxy/pkg/logger" ) // HTTPVerb defines the restful api http verb @@ -119,7 +119,7 @@ func LoadAPIConfigFromFile(path string) (*APIConfig, error) { if len(path) == 0 { return nil, perrors.Errorf("Config file not specified") } - log.Printf("Load API configuration file form %s", path) + logger.Info("Load API configuration file form %s", path) apiConf := &APIConfig{} _, err := yaml.UnmarshalYMLConfig(path, apiConf) if err != nil { diff --git a/pkg/config/api_config_test.go b/pkg/config/api_config_test.go index 0644ea9a3..c5d18df82 100644 --- a/pkg/config/api_config_test.go +++ b/pkg/config/api_config_test.go @@ -23,11 +23,14 @@ import ( ) import ( - "github.com/dubbogo/dubbo-go-proxy/pkg/config" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" ) +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" +) + func TestLoadAPIConfigFromFile(t *testing.T) { apiC, err := config.LoadAPIConfigFromFile("") assert.Empty(t, apiC) From c64e07878c73078df8fde20366074ae8c481442a Mon Sep 17 00:00:00 2001 From: william feng <> Date: Tue, 22 Sep 2020 15:49:12 +0800 Subject: [PATCH 06/18] remove the unnecessary return from unmarshalymlconfig function --- pkg/common/yaml/yaml.go | 6 +++--- pkg/common/yaml/yaml_test.go | 6 +++--- pkg/config/api_config.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/common/yaml/yaml.go b/pkg/common/yaml/yaml.go index de67bfca5..47c65123f 100644 --- a/pkg/common/yaml/yaml.go +++ b/pkg/common/yaml/yaml.go @@ -41,12 +41,12 @@ func LoadYMLConfig(confProFile string) ([]byte, error) { } // UnmarshalYMLConfig Load yml config byte from file, then unmarshal to object -func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { +func UnmarshalYMLConfig(confProFile string, out interface{}) error { confFileStream, err := LoadYMLConfig(confProFile) if err != nil { - return confFileStream, perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) + return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) } - return confFileStream, yaml.Unmarshal(confFileStream, out) + return yaml.Unmarshal(confFileStream, out) } //UnmarshalYML unmarshals decodes the first document found within the in byte slice and assigns decoded values into the out value. diff --git a/pkg/common/yaml/yaml_test.go b/pkg/common/yaml/yaml_test.go index 5a271a258..0380a4f3e 100644 --- a/pkg/common/yaml/yaml_test.go +++ b/pkg/common/yaml/yaml_test.go @@ -30,7 +30,7 @@ func TestUnmarshalYMLConfig(t *testing.T) { conPath, err := filepath.Abs("./testdata/config.yml") assert.NoError(t, err) c := &Config{} - _, err = UnmarshalYMLConfig(conPath, c) + err = UnmarshalYMLConfig(conPath, c) assert.NoError(t, err) assert.Equal(t, "strTest", c.StrTest) assert.Equal(t, 11, c.IntTest) @@ -40,9 +40,9 @@ func TestUnmarshalYMLConfig(t *testing.T) { func TestUnmarshalYMLConfigError(t *testing.T) { c := &Config{} - _, err := UnmarshalYMLConfig("./testdata/config", c) + err := UnmarshalYMLConfig("./testdata/config", c) assert.Error(t, err) - _, err = UnmarshalYMLConfig("", c) + err = UnmarshalYMLConfig("", c) assert.Error(t, err) } diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index bfc02f301..97638a769 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -121,7 +121,7 @@ func LoadAPIConfigFromFile(path string) (*APIConfig, error) { } logger.Info("Load API configuration file form %s", path) apiConf := &APIConfig{} - _, err := yaml.UnmarshalYMLConfig(path, apiConf) + err := yaml.UnmarshalYMLConfig(path, apiConf) if err != nil { return nil, perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } From ee4384224c16e9676351680ddcdad57c523c064e Mon Sep 17 00:00:00 2001 From: william feng <> Date: Tue, 22 Sep 2020 22:14:24 +0800 Subject: [PATCH 07/18] remove content-type from definition --- configs/api_config.yaml | 1 - pkg/config/api_config.go | 1 - pkg/config/mock/api_config.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/configs/api_config.yaml b/configs/api_config.yaml index 35ab2d5bf..b41c38089 100644 --- a/configs/api_config.yaml +++ b/configs/api_config.yaml @@ -22,7 +22,6 @@ resources: required: false requestBody: - definitionName: modelDefinition - contentType: application/json integrationRequest: type: dubbo mappingParams: diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 97638a769..b869b2a8c 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -92,7 +92,6 @@ type Params struct { // BodyDefinition connects the request body to the definitions type BodyDefinition struct { DefinitionName string `yaml:"definitionName"` - ContentType string `yaml:"contentType"` } // IntegrationRequest defines the backend request format and target diff --git a/pkg/config/mock/api_config.yml b/pkg/config/mock/api_config.yml index 80769333e..9e0f104b0 100644 --- a/pkg/config/mock/api_config.yml +++ b/pkg/config/mock/api_config.yml @@ -22,7 +22,6 @@ resources: required: false requestBody: - definitionName: modelDefinition - contentType: application/json integrationRequest: requestType: dubbo mappingParams: From d542fe5a3e1a6044cb2aaf7d1f857d498f21eab9 Mon Sep 17 00:00:00 2001 From: tiecheng Date: Thu, 24 Sep 2020 15:56:03 +0800 Subject: [PATCH 08/18] dingtalk event pr 2 prt --- .github/workflows/github-actions.yml | 182 +++++++++++++-------------- 1 file changed, 89 insertions(+), 93 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index 06b812c78..e4816633c 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -2,9 +2,11 @@ name: CI on: push: - branches: [master, develop] + branches: [ master, develop ] pull_request: branches: "*" + pull_request_target: + branches: "*" jobs: @@ -26,97 +28,91 @@ jobs: steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go_version }} - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Cache dependencies - uses: actions/cache@v2 - with: - # Cache - path: ~/go/pkg/mod - # Cache key - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - # An ordered list of keys to use for restoring the cache if no cache hit occurred for key - restore-keys: | - ${{ runner.os }}-go- - - - name: Get dependencies - run: | - if [ -f Gopkg.toml ]; then - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - dep ensure - else - go get -v -t -d ./... - fi - - - name: Go Fmt - run: go fmt ./... && [[ -z `git status -s` ]] - - - name: License Check - run: | - go fmt ./... && [[ -z `git status -s` ]] - sh before_validate_license.sh - chmod u+x /tmp/tools/license/license-header-checker - /tmp/tools/license/license-header-checker -v -a -r -i vendor /tmp/tools/license/license.txt . go && [[ -z `git status -s` ]] - - - name: Go Test - run: GO111MODULE=on && go mod vendor && go test ./... -bench . -race -v -coverprofile=coverage.txt - -# - name: Test -# run: | -# chmod u+x before_ut.sh && ./before_ut.sh -# go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic -# chmod +x integrate_test.sh && ./integrate_test.sh - - - name: Coverage - run: bash <(curl -s https://codecov.io/bash) - - - name: Hello world - run: echo Hello world ${{ secrets.PROXY_DING_TOKEN }} ${{ secrets.PROXY_DING_SIGN }} - - # Because the contexts of push and PR are different, there are two Notify. - # Notifications are triggered only in the dubbogo/dubbo-go-proxy repository. - - name: DingTalk Message Notify only Push - uses: zcong1993/actions-ding@v3.0.1 - # Whether job is successful or not, always () is always true. - if: | - always() && - github.event_name == 'push' && - github.repository == 'dubbogo/dubbo-go-proxy' - with: - # DingDing bot token - dingToken: ${{ env.DING_TOKEN }} - secret: ${{ env.DING_SIGN }} - # Post Body to send - body: | - { - "msgtype": "markdown", - "markdown": { - "title": "Github Actions", - "text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: ${{ github.ref }} \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n - SHA: [${{ github.sha }}](${{ github.event.compare }})" + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go_version }} + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + # Cache + path: ~/go/pkg/mod + # Cache key + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # An ordered list of keys to use for restoring the cache if no cache hit occurred for key + restore-keys: | + ${{ runner.os }}-go- + + - name: Get dependencies + run: | + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + else + go get -v -t -d ./... + fi + + - name: Go Fmt + run: go fmt ./... && [[ -z `git status -s` ]] + + - name: License Check + run: | + go fmt ./... && [[ -z `git status -s` ]] + sh before_validate_license.sh + chmod u+x /tmp/tools/license/license-header-checker + /tmp/tools/license/license-header-checker -v -a -r -i vendor /tmp/tools/license/license.txt . go && [[ -z `git status -s` ]] + + - name: Go Test + run: GO111MODULE=on && go mod vendor && go test ./... -bench . -race -v -coverprofile=coverage.txt + + - name: Coverage + run: bash <(curl -s https://codecov.io/bash) + + - name: Hello world + run: echo Hello world ${{ secrets.PROXY_DING_TOKEN }} ${{ secrets.PROXY_DING_SIGN }} + + # Because the contexts of push and PR are different, there are two Notify. + # Notifications are triggered only in the dubbogo/dubbo-go-proxy repository. + - name: DingTalk Message Notify only Push + uses: zcong1993/actions-ding@v3.0.1 + # Whether job is successful or not, always () is always true. + if: | + always() && + github.event_name == 'push' && + github.repository == 'dubbogo/dubbo-go-proxy' + with: + # DingDing bot token + dingToken: ${{ env.DING_TOKEN }} + secret: ${{ env.DING_SIGN }} + # Post Body to send + body: | + { + "msgtype": "markdown", + "markdown": { + "title": "Github Actions", + "text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: ${{ github.ref }} \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n - SHA: [${{ github.sha }}](${{ github.event.compare }})" + } } - } - - - name: DingTalk Message Notify only PR - uses: zcong1993/actions-ding@v3.0.1 - if: | - always() && - github.event_name == 'pull_request' && - github.repository == 'dubbogo/dubbo-go-proxy' - with: - dingToken: ${{ env.DING_TOKEN }} - secret: ${{ env.DING_SIGN }} - body: | - { - "msgtype": "markdown", - "markdown": { - "title": "Github Actions", - "text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - pr_title: ${{ github.event.pull_request.title }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: [${{ github.ref }}](${{ github.event.pull_request._links.html.href }}) \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n > SHA: [${{ github.sha }}](${{ github.event.pull_request._links.html.href }})" + + - name: DingTalk Message Notify only PR + uses: zcong1993/actions-ding@v3.0.1 + if: | + always() && + github.event_name == 'pull_request_target' && + github.repository == 'dubbogo/dubbo-go-proxy' + with: + dingToken: ${{ env.DING_TOKEN }} + secret: ${{ env.DING_SIGN }} + body: | + { + "msgtype": "markdown", + "markdown": { + "title": "Github Actions", + "text": "## Github Actions \n - name: CI \n - repository: ${{ github.repository }} \n - pr_title: ${{ github.event.pull_request.title }} \n - trigger: ${{ github.actor }} \n - event: ${{ github.event_name }} \n - ref: [${{ github.ref }}](${{ github.event.pull_request._links.html.href }}) \n - status: [${{ job.status }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) \n - environment: ${{ runner.os }} \n > SHA: [${{ github.sha }}](${{ github.event.pull_request._links.html.href }})" + } } - } From a2ea5632cdd0c37793e3b3b4b6422d045b914e82 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Mon, 21 Sep 2020 13:51:04 +0800 Subject: [PATCH 09/18] added router tree and node Signed-off-by: williamfeng323 --- go.mod | 3 +- go.sum | 4 +- pkg/config/api_config.go | 4 +- pkg/config/mock/api_config.yml | 6 +-- pkg/router/route.go | 55 ++++++++++++++++++++ pkg/router/route_test.go | 79 +++++++++++++++++++++++++++++ pkg/router/router_tree.go | 93 ++++++++++++++++++++++++++++++++++ pkg/router/router_tree_test.go | 89 ++++++++++++++++++++++++++++++++ 8 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 pkg/router/route.go create mode 100644 pkg/router/route_test.go create mode 100644 pkg/router/router_tree.go create mode 100644 pkg/router/router_tree_test.go diff --git a/go.mod b/go.mod index c0782e824..c4a449479 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/apache/dubbo-go v1.5.1 + github.com/emirpasic/gods v1.12.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/goinggo/mapstructure v0.0.0-20140717182941-194205d9b4a9 github.com/pkg/errors v0.9.1 @@ -11,4 +12,4 @@ require ( github.com/urfave/cli v1.22.4 go.uber.org/zap v1.15.0 gopkg.in/yaml.v2 v2.2.8 -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 4ffe74917..b02061ac5 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -713,4 +715,4 @@ k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= \ No newline at end of file +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index b869b2a8c..3c9126d4e 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -62,14 +62,14 @@ type Resource struct { Type string `yaml:"type"` // Restful, Dubbo Path string `yaml:"path"` Description string `yaml:"description"` - Filters []string `yaml:"filters"` Methods []Method `yaml:"methods"` Resources []Resource `yaml:"resources,omitempty"` } // Method defines the method of the api type Method struct { - OnAir bool `yaml:"onAir"` + OnAir bool `yaml:"onAir"` + Filters []string `yaml:"filters"` HTTPVerb `yaml:"httpVerb"` InboundRequest `yaml:"inboundRequest"` IntegrationRequest `yaml:"integrationRequest"` diff --git a/pkg/config/mock/api_config.yml b/pkg/config/mock/api_config.yml index 9e0f104b0..632f49f12 100644 --- a/pkg/config/mock/api_config.yml +++ b/pkg/config/mock/api_config.yml @@ -4,11 +4,11 @@ resources: - path: '/' type: restful description: resource documentation - filters: - - filterA - - filterB methods: - httpVerb: GET + filters: + - filterA + - filterB onAir: true inboundRequest: requestType: http diff --git a/pkg/router/route.go b/pkg/router/route.go new file mode 100644 index 000000000..846bbc779 --- /dev/null +++ b/pkg/router/route.go @@ -0,0 +1,55 @@ +package router + +import ( + "path" + "strings" +) + +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/emirpasic/gods/trees/avltree" + "github.com/pkg/errors" +) + +// RouterGroup a easy way to manage the actual router tree, provides the apis to group the routers +type RouterGroup struct { + root bool + basePath string + routerTree *avltree.Tree +} + +// Group deviates a new router group from current group. use the same routerTree. +func (rg *RouterGroup) Group(relativePath string) (*RouterGroup, error) { + if len(relativePath) == 0 { + return nil, errors.New("Cannot group router with empty path") + } + if relativePath[0] != '/' { + return nil, errors.New("Path must start with '/'") + } + return &RouterGroup{ + basePath: rg.absolutePath(relativePath), + routerTree: rg.routerTree, + }, nil +} + +func (rg *RouterGroup) absolutePath(relativePath string) string { + if len(relativePath) == 0 { + return rg.basePath + } + return strings.TrimRight(path.Join(rg.basePath, relativePath), "/") +} + +// Add adds the new router node to the group +func (rg *RouterGroup) Add(path string, method config.Method) error { + rg.routerTree.Put(path, method) + return nil +} + +// NewRouter returns a nil tree root router group +func NewRouter() *RouterGroup { + return &RouterGroup{ + root: true, + basePath: "/", + routerTree: avltree.NewWithStringComparator(), + } +} diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go new file mode 100644 index 000000000..e674410d1 --- /dev/null +++ b/pkg/router/route_test.go @@ -0,0 +1,79 @@ +package router + +import ( + "testing" +) + +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/stretchr/testify/assert" +) + +func getMockMethod(verb config.HTTPVerb) config.Method { + inbound := config.InboundRequest{} + integration := config.IntegrationRequest{} + return config.Method{ + OnAir: true, + HTTPVerb: verb, + InboundRequest: inbound, + IntegrationRequest: integration, + } +} + +func TestAbsolutePath(t *testing.T) { + rg := &RouterGroup{ + basePath: "/", + } + rst := rg.absolutePath("abc") + assert.Equal(t, rst, "/abc") + + rst = rg.absolutePath("") + assert.Equal(t, rst, "/") + + rg = &RouterGroup{ + basePath: "/a", + } + + rst = rg.absolutePath("") + assert.Equal(t, rst, "/a") + + rst = rg.absolutePath("b") + assert.Equal(t, rst, "/a/b") + + rst = rg.absolutePath("/b/") + assert.Equal(t, rst, "/a/b") + + rst = rg.absolutePath("/:id") + assert.Equal(t, rst, "/a/:id") +} + +func TestGroup(t *testing.T) { + rg := &RouterGroup{ + basePath: "/", + root: true, + } + rg1, err := rg.Group("") + assert.Nil(t, rg1) + assert.Error(t, err, "Cannot group router with empty path") + + rg2, err := rg.Group("/test") + assert.Equal(t, rg2.basePath, "/test") + assert.Nil(t, err) + + rg3, err := rg2.Group("mock") + assert.Nil(t, rg3) + assert.Error(t, err, "Path must start with '/'") + + rg4, err := rg2.Group("/mock") + assert.Nil(t, err) + assert.Equal(t, rg4.basePath, "/test/mock") +} + +func TestAdd(t *testing.T) { + rg := NewRouter() + rg.Add("/", getMockMethod(config.MethodGet)) + _, ok := rg.routerTree.Get("/") + assert.True(t, ok) + + // rg.Add("/") +} diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go new file mode 100644 index 000000000..4e9f36862 --- /dev/null +++ b/pkg/router/router_tree.go @@ -0,0 +1,93 @@ +package router + +import ( + "fmt" + "strings" + "sync" +) + +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/emirpasic/gods/trees/avltree" + "github.com/pkg/errors" +) + +// RouterNode defines the single method of the router configured API +type RouterNode struct { + fullPath string + wildcard bool + methods map[config.HTTPVerb]config.Method + lock sync.RWMutex +} + +// RouterTree defines the tree of router APIs +type RouterTree struct { + tree *avltree.Tree + wildcardTree *avltree.Tree +} + +// Put put a key val into the tree +func (rt *RouterTree) Put(fullPath string, method config.Method) error { + fullPath = strings.ToLower(fullPath) + node, ok := rt.tree.Get(fullPath) + + if !ok { + ms := make(map[config.HTTPVerb]config.Method) + wildcard := isWildcard(fullPath) + rn := &RouterNode{ + fullPath: fullPath, + methods: ms, + wildcard: wildcard, + } + rn.methods[method.HTTPVerb] = method + if wildcard { + rt.wildcardTree.Put(fullPath, rn) + } + rt.tree.Put(fullPath, rn) + return nil + } + if _, ok := node.(*RouterNode).methods[method.HTTPVerb]; ok { + return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) + } + node.(*RouterNode).methods[method.HTTPVerb] = method + return nil +} + +func (rt *RouterTree) searchWildcard(fullPath string) (*RouterNode, bool) { + resources := strings.Split(fullPath[1:], "/") + resources[0] = "/" + resources[0] + wildcardPaths := rt.wildcardTree.Keys() + + for _, p := range wildcardPaths { + if wildcardMatch(p.(string), fullPath) { + n, ok := rt.wildcardTree.Get(p) + return n.(*RouterNode), ok + } + } + // parmPositions := []int{} + // for i := range resources { + // if isWildcard(resources[i]) { + // parmPositions = append(parmPositions, i) + // } + // } + // for _, position := range parmPositions { + // searchPath := strings.Join(resources[:position], "/") + // if node, ok := rt.tree.Get(searchPath); ok { + // return node.(*RouterNode), ok + // } + // } + return nil, false +} + +func isWildcard(fullPath string) bool { + for _, s := range fullPath { + if s == ':' { + return true + } + } + return false +} + +func wildcardMatch(wildcardPath string, targetPath string) bool { + return false +} diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go new file mode 100644 index 000000000..bd3e580f7 --- /dev/null +++ b/pkg/router/router_tree_test.go @@ -0,0 +1,89 @@ +package router + +import ( + "testing" +) + +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/emirpasic/gods/trees/avltree" + "github.com/stretchr/testify/assert" +) + +func TestPut(t *testing.T) { + rt := &RouterTree{ + tree: avltree.NewWithStringComparator(), + } + n0 := getMockMethod(config.MethodGet) + rt.Put("/", n0) + _, ok := rt.tree.Get("/") + assert.True(t, ok) + + err := rt.Put("/", n0) + assert.Error(t, err, "Method GET already exists in path /") + + n1 := getMockMethod(config.MethodPost) + err = rt.Put("/mock", n0) + assert.Nil(t, err) + err = rt.Put("/mock", n1) + assert.Nil(t, err) + mNode, ok := rt.tree.Get("/mock") + assert.True(t, ok) + assert.Equal(t, len(mNode.(*RouterNode).methods), 2) + + err = rt.Put("/mock/test", n0) + assert.Nil(t, err) + _, ok = rt.tree.Get("/mock") + assert.True(t, ok) + + rt.Put("/test/:id", n0) + tNode, ok := rt.tree.Get("/test/:id") + assert.True(t, ok) + assert.True(t, tNode.(*RouterNode).wildcard) + + err = rt.Put("/test/:id", n1) + assert.Nil(t, err) + err = rt.Put("/test/js", n0) + assert.Error(t, err, "/test/:id wildcard already exist so that cannot add path /test/js") + + err = rt.Put("/test/:id/mock", n0) + tNode, ok = rt.tree.Get("/test/:id/mock") + assert.True(t, ok) + assert.True(t, tNode.(*RouterNode).wildcard) + assert.Nil(t, err) +} + +func TestSearchWildcard(t *testing.T) { + rt := &RouterTree{ + tree: avltree.NewWithStringComparator(), + } + n0 := getMockMethod(config.MethodGet) + e := rt.Put("/theboys", n0) + assert.Nil(t, e) + e = rt.Put("/theboys/:id", n0) + assert.Nil(t, e) + e = rt.Put("/vought/:id/supe/:name", n0) + assert.Nil(t, e) + + _, ok := rt.searchWildcard("/marvel") + assert.False(t, ok) + _, ok = rt.searchWildcard("/theboys/:id/age") + assert.False(t, ok) + _, ok = rt.searchWildcard("/theboys/butcher") + assert.True(t, ok) + _, ok = rt.searchWildcard("/vought/:id/supe/homelander") + assert.True(t, ok) +} + +func TestIsWildcard(t *testing.T) { + assert.True(t, isWildcard("/test/:id")) + assert.False(t, isWildcard("/test")) + assert.True(t, isWildcard("/test/:id/mock")) +} + +func TestWildcardMatch(t *testing.T) { + assert.True(t, wildcardMatch("/vought/:id", "/vought/12345")) + assert.True(t, wildcardMatch("/vought/:id", "/vought/125abc")) + assert.False(t, wildcardMatch("/vought/:id", "/vought/1234abcd/status")) + assert.True(t, wildcardMatch("/voughT/:id/:action", "/Vought/1234abcd/attack")) +} From 3e9b6df3257e37258a756b6b1e98bde54622d358 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Tue, 22 Sep 2020 12:10:14 +0800 Subject: [PATCH 10/18] router search wildcard path Signed-off-by: williamfeng323 --- pkg/router/router_tree.go | 35 ++++++++++++++++------------------ pkg/router/router_tree_test.go | 9 +++++---- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go index 4e9f36862..442b21987 100644 --- a/pkg/router/router_tree.go +++ b/pkg/router/router_tree.go @@ -33,7 +33,7 @@ func (rt *RouterTree) Put(fullPath string, method config.Method) error { if !ok { ms := make(map[config.HTTPVerb]config.Method) - wildcard := isWildcard(fullPath) + wildcard := containParam(fullPath) rn := &RouterNode{ fullPath: fullPath, methods: ms, @@ -54,32 +54,17 @@ func (rt *RouterTree) Put(fullPath string, method config.Method) error { } func (rt *RouterTree) searchWildcard(fullPath string) (*RouterNode, bool) { - resources := strings.Split(fullPath[1:], "/") - resources[0] = "/" + resources[0] wildcardPaths := rt.wildcardTree.Keys() - for _, p := range wildcardPaths { if wildcardMatch(p.(string), fullPath) { n, ok := rt.wildcardTree.Get(p) return n.(*RouterNode), ok } } - // parmPositions := []int{} - // for i := range resources { - // if isWildcard(resources[i]) { - // parmPositions = append(parmPositions, i) - // } - // } - // for _, position := range parmPositions { - // searchPath := strings.Join(resources[:position], "/") - // if node, ok := rt.tree.Get(searchPath); ok { - // return node.(*RouterNode), ok - // } - // } return nil, false } -func isWildcard(fullPath string) bool { +func containParam(fullPath string) bool { for _, s := range fullPath { if s == ':' { return true @@ -88,6 +73,18 @@ func isWildcard(fullPath string) bool { return false } -func wildcardMatch(wildcardPath string, targetPath string) bool { - return false +func wildcardMatch(wildcardPath string, checkPath string) bool { + wildcardPath = strings.ToLower(wildcardPath) + checkPath = strings.ToLower(checkPath) + wPathSplit := strings.Split(wildcardPath[1:], "/") + cPathSplit := strings.Split(checkPath[1:], "/") + if len(wPathSplit) != len(cPathSplit) { + return false + } + for i, s := range wPathSplit { + if containParam(s) { + cPathSplit[i] = s + } + } + return strings.Join(wPathSplit, "/") == strings.Join(cPathSplit, "/") } diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go index bd3e580f7..5fdc20a1d 100644 --- a/pkg/router/router_tree_test.go +++ b/pkg/router/router_tree_test.go @@ -56,6 +56,7 @@ func TestPut(t *testing.T) { func TestSearchWildcard(t *testing.T) { rt := &RouterTree{ tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), } n0 := getMockMethod(config.MethodGet) e := rt.Put("/theboys", n0) @@ -75,10 +76,10 @@ func TestSearchWildcard(t *testing.T) { assert.True(t, ok) } -func TestIsWildcard(t *testing.T) { - assert.True(t, isWildcard("/test/:id")) - assert.False(t, isWildcard("/test")) - assert.True(t, isWildcard("/test/:id/mock")) +func TestContainParam(t *testing.T) { + assert.True(t, containParam("/test/:id")) + assert.False(t, containParam("/test")) + assert.True(t, containParam("/test/:id/mock")) } func TestWildcardMatch(t *testing.T) { From e7f200fe4aa1157a65e4fade35f8ff85d70f8604 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Wed, 23 Sep 2020 14:09:49 +0800 Subject: [PATCH 11/18] completed router tree add Signed-off-by: williamfeng323 --- pkg/router/router_tree.go | 16 ++++++++++++++-- pkg/router/router_tree_test.go | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go index 442b21987..8dd505942 100644 --- a/pkg/router/router_tree.go +++ b/pkg/router/router_tree.go @@ -29,11 +29,15 @@ type RouterTree struct { // Put put a key val into the tree func (rt *RouterTree) Put(fullPath string, method config.Method) error { fullPath = strings.ToLower(fullPath) - node, ok := rt.tree.Get(fullPath) + wildcard := containParam(fullPath) + + if wildcardNode, found := rt.searchWildcard(fullPath); found { + return putMethod(wildcardNode, method) + } + node, ok := rt.tree.Get(fullPath) if !ok { ms := make(map[config.HTTPVerb]config.Method) - wildcard := containParam(fullPath) rn := &RouterNode{ fullPath: fullPath, methods: ms, @@ -64,6 +68,14 @@ func (rt *RouterTree) searchWildcard(fullPath string) (*RouterNode, bool) { return nil, false } +func putMethod(node *RouterNode, method config.Method) error { + if _, ok := node.methods[method.HTTPVerb]; ok { + return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) + } + node.methods[method.HTTPVerb] = method + return nil +} + func containParam(fullPath string) bool { for _, s := range fullPath { if s == ':' { diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go index 5fdc20a1d..3dcc22be7 100644 --- a/pkg/router/router_tree_test.go +++ b/pkg/router/router_tree_test.go @@ -12,7 +12,8 @@ import ( func TestPut(t *testing.T) { rt := &RouterTree{ - tree: avltree.NewWithStringComparator(), + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), } n0 := getMockMethod(config.MethodGet) rt.Put("/", n0) @@ -55,7 +56,7 @@ func TestPut(t *testing.T) { func TestSearchWildcard(t *testing.T) { rt := &RouterTree{ - tree: avltree.NewWithStringComparator(), + tree: avltree.NewWithStringComparator(), wildcardTree: avltree.NewWithStringComparator(), } n0 := getMockMethod(config.MethodGet) From cb4af3e7403de0883e01e62c16bf0318a0139d5e Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Wed, 23 Sep 2020 14:40:29 +0800 Subject: [PATCH 12/18] added the license Signed-off-by: williamfeng323 --- pkg/router/route.go | 17 +++++++++++++++++ pkg/router/route_test.go | 17 +++++++++++++++++ pkg/router/router_tree.go | 17 +++++++++++++++++ pkg/router/router_tree_test.go | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/pkg/router/route.go b/pkg/router/route.go index 846bbc779..0c433ae27 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -1,3 +1,20 @@ +/* + * 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 ( diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go index e674410d1..716e06405 100644 --- a/pkg/router/route_test.go +++ b/pkg/router/route_test.go @@ -1,3 +1,20 @@ +/* + * 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 ( diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go index 8dd505942..4ed824c0c 100644 --- a/pkg/router/router_tree.go +++ b/pkg/router/router_tree.go @@ -1,3 +1,20 @@ +/* + * 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 ( diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go index 3dcc22be7..2fd09bea1 100644 --- a/pkg/router/router_tree_test.go +++ b/pkg/router/router_tree_test.go @@ -1,3 +1,20 @@ +/* + * 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 ( From 73a5fe38faa0e20f48afa02709de54afce3226ce Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Wed, 23 Sep 2020 15:22:27 +0800 Subject: [PATCH 13/18] change the struct name to fulfil haund Signed-off-by: williamfeng323 --- pkg/router/route.go | 16 ++++++++-------- pkg/router/route_test.go | 6 +++--- pkg/router/router_tree.go | 22 +++++++++++----------- pkg/router/router_tree_test.go | 10 +++++----- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pkg/router/route.go b/pkg/router/route.go index 0c433ae27..e372f5306 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -28,28 +28,28 @@ import ( "github.com/pkg/errors" ) -// RouterGroup a easy way to manage the actual router tree, provides the apis to group the routers -type RouterGroup struct { +// Group a easy way to manage the actual router tree, provides the apis to group the routers +type Group struct { root bool basePath string routerTree *avltree.Tree } // Group deviates a new router group from current group. use the same routerTree. -func (rg *RouterGroup) Group(relativePath string) (*RouterGroup, error) { +func (rg *Group) Group(relativePath string) (*Group, error) { if len(relativePath) == 0 { return nil, errors.New("Cannot group router with empty path") } if relativePath[0] != '/' { return nil, errors.New("Path must start with '/'") } - return &RouterGroup{ + return &Group{ basePath: rg.absolutePath(relativePath), routerTree: rg.routerTree, }, nil } -func (rg *RouterGroup) absolutePath(relativePath string) string { +func (rg *Group) absolutePath(relativePath string) string { if len(relativePath) == 0 { return rg.basePath } @@ -57,14 +57,14 @@ func (rg *RouterGroup) absolutePath(relativePath string) string { } // Add adds the new router node to the group -func (rg *RouterGroup) Add(path string, method config.Method) error { +func (rg *Group) Add(path string, method config.Method) error { rg.routerTree.Put(path, method) return nil } // NewRouter returns a nil tree root router group -func NewRouter() *RouterGroup { - return &RouterGroup{ +func NewRouter() *Group { + return &Group{ root: true, basePath: "/", routerTree: avltree.NewWithStringComparator(), diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go index 716e06405..fe0766014 100644 --- a/pkg/router/route_test.go +++ b/pkg/router/route_test.go @@ -38,7 +38,7 @@ func getMockMethod(verb config.HTTPVerb) config.Method { } func TestAbsolutePath(t *testing.T) { - rg := &RouterGroup{ + rg := &Group{ basePath: "/", } rst := rg.absolutePath("abc") @@ -47,7 +47,7 @@ func TestAbsolutePath(t *testing.T) { rst = rg.absolutePath("") assert.Equal(t, rst, "/") - rg = &RouterGroup{ + rg = &Group{ basePath: "/a", } @@ -65,7 +65,7 @@ func TestAbsolutePath(t *testing.T) { } func TestGroup(t *testing.T) { - rg := &RouterGroup{ + rg := &Group{ basePath: "/", root: true, } diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go index 4ed824c0c..4fa1a6c76 100644 --- a/pkg/router/router_tree.go +++ b/pkg/router/router_tree.go @@ -29,22 +29,22 @@ import ( "github.com/pkg/errors" ) -// RouterNode defines the single method of the router configured API -type RouterNode struct { +// Node defines the single method of the router configured API +type Node struct { fullPath string wildcard bool methods map[config.HTTPVerb]config.Method lock sync.RWMutex } -// RouterTree defines the tree of router APIs -type RouterTree struct { +// Tree defines the tree of router APIs +type Tree struct { tree *avltree.Tree wildcardTree *avltree.Tree } // Put put a key val into the tree -func (rt *RouterTree) Put(fullPath string, method config.Method) error { +func (rt *Tree) Put(fullPath string, method config.Method) error { fullPath = strings.ToLower(fullPath) wildcard := containParam(fullPath) @@ -55,7 +55,7 @@ func (rt *RouterTree) Put(fullPath string, method config.Method) error { node, ok := rt.tree.Get(fullPath) if !ok { ms := make(map[config.HTTPVerb]config.Method) - rn := &RouterNode{ + rn := &Node{ fullPath: fullPath, methods: ms, wildcard: wildcard, @@ -67,25 +67,25 @@ func (rt *RouterTree) Put(fullPath string, method config.Method) error { rt.tree.Put(fullPath, rn) return nil } - if _, ok := node.(*RouterNode).methods[method.HTTPVerb]; ok { + if _, ok := node.(*Node).methods[method.HTTPVerb]; ok { return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) } - node.(*RouterNode).methods[method.HTTPVerb] = method + node.(*Node).methods[method.HTTPVerb] = method return nil } -func (rt *RouterTree) searchWildcard(fullPath string) (*RouterNode, bool) { +func (rt *Tree) searchWildcard(fullPath string) (*Node, bool) { wildcardPaths := rt.wildcardTree.Keys() for _, p := range wildcardPaths { if wildcardMatch(p.(string), fullPath) { n, ok := rt.wildcardTree.Get(p) - return n.(*RouterNode), ok + return n.(*Node), ok } } return nil, false } -func putMethod(node *RouterNode, method config.Method) error { +func putMethod(node *Node, method config.Method) error { if _, ok := node.methods[method.HTTPVerb]; ok { return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) } diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go index 2fd09bea1..6823126ad 100644 --- a/pkg/router/router_tree_test.go +++ b/pkg/router/router_tree_test.go @@ -28,7 +28,7 @@ import ( ) func TestPut(t *testing.T) { - rt := &RouterTree{ + rt := &Tree{ tree: avltree.NewWithStringComparator(), wildcardTree: avltree.NewWithStringComparator(), } @@ -47,7 +47,7 @@ func TestPut(t *testing.T) { assert.Nil(t, err) mNode, ok := rt.tree.Get("/mock") assert.True(t, ok) - assert.Equal(t, len(mNode.(*RouterNode).methods), 2) + assert.Equal(t, len(mNode.(*Node).methods), 2) err = rt.Put("/mock/test", n0) assert.Nil(t, err) @@ -57,7 +57,7 @@ func TestPut(t *testing.T) { rt.Put("/test/:id", n0) tNode, ok := rt.tree.Get("/test/:id") assert.True(t, ok) - assert.True(t, tNode.(*RouterNode).wildcard) + assert.True(t, tNode.(*Node).wildcard) err = rt.Put("/test/:id", n1) assert.Nil(t, err) @@ -67,12 +67,12 @@ func TestPut(t *testing.T) { err = rt.Put("/test/:id/mock", n0) tNode, ok = rt.tree.Get("/test/:id/mock") assert.True(t, ok) - assert.True(t, tNode.(*RouterNode).wildcard) + assert.True(t, tNode.(*Node).wildcard) assert.Nil(t, err) } func TestSearchWildcard(t *testing.T) { - rt := &RouterTree{ + rt := &Tree{ tree: avltree.NewWithStringComparator(), wildcardTree: avltree.NewWithStringComparator(), } From 0540fa93a7ea771b5b759abdf8607638e0c23341 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Wed, 23 Sep 2020 16:12:29 +0800 Subject: [PATCH 14/18] find method by path Signed-off-by: williamfeng323 --- pkg/router/router_tree.go | 32 +++++++++++++++++++++++++++----- pkg/router/router_tree_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go index 4fa1a6c76..aff531dfe 100644 --- a/pkg/router/router_tree.go +++ b/pkg/router/router_tree.go @@ -33,7 +33,7 @@ import ( type Node struct { fullPath string wildcard bool - methods map[config.HTTPVerb]config.Method + methods map[config.HTTPVerb]*config.Method lock sync.RWMutex } @@ -54,13 +54,13 @@ func (rt *Tree) Put(fullPath string, method config.Method) error { node, ok := rt.tree.Get(fullPath) if !ok { - ms := make(map[config.HTTPVerb]config.Method) + ms := make(map[config.HTTPVerb]*config.Method) rn := &Node{ fullPath: fullPath, methods: ms, wildcard: wildcard, } - rn.methods[method.HTTPVerb] = method + rn.methods[method.HTTPVerb] = &method if wildcard { rt.wildcardTree.Put(fullPath, rn) } @@ -70,10 +70,24 @@ func (rt *Tree) Put(fullPath string, method config.Method) error { if _, ok := node.(*Node).methods[method.HTTPVerb]; ok { return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) } - node.(*Node).methods[method.HTTPVerb] = method + node.(*Node).methods[method.HTTPVerb] = &method return nil } +// FindMethod returns the api that meets the +func (rt *Tree) FindMethod(fullPath string, method config.HTTPVerb) (*config.Method, bool) { + var n interface{} + var found bool + if n, found = rt.searchWildcard(fullPath); !found { + n, found = rt.tree.Get(fullPath) + } + if found { + method, ok := n.(*Node).methods[method] + return method, ok + } + return nil, false +} + func (rt *Tree) searchWildcard(fullPath string) (*Node, bool) { wildcardPaths := rt.wildcardTree.Keys() for _, p := range wildcardPaths { @@ -89,7 +103,7 @@ func putMethod(node *Node, method config.Method) error { if _, ok := node.methods[method.HTTPVerb]; ok { return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) } - node.methods[method.HTTPVerb] = method + node.methods[method.HTTPVerb] = &method return nil } @@ -117,3 +131,11 @@ func wildcardMatch(wildcardPath string, checkPath string) bool { } return strings.Join(wPathSplit, "/") == strings.Join(cPathSplit, "/") } + +// NewTree returns an empty router tree +func NewTree() *Tree { + return &Tree{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), + } +} diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go index 6823126ad..9221fed49 100644 --- a/pkg/router/router_tree_test.go +++ b/pkg/router/router_tree_test.go @@ -71,6 +71,33 @@ func TestPut(t *testing.T) { assert.Nil(t, err) } +func TestFindMethod(t *testing.T) { + rt := &Tree{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), + } + n0 := getMockMethod(config.MethodGet) + n1 := getMockMethod(config.MethodPost) + e := rt.Put("/theboys", n0) + assert.Nil(t, e) + e = rt.Put("/theboys/:id", n0) + assert.Nil(t, e) + e = rt.Put("/vought/:id/supe/:name", n1) + assert.Nil(t, e) + + m, ok := rt.FindMethod("/theboys", config.MethodGet) + assert.True(t, ok) + assert.NotNil(t, m) + + m, ok = rt.FindMethod("/theboys", config.MethodPost) + assert.False(t, ok) + assert.Nil(t, m) + + m, ok = rt.FindMethod("/vought/123/supe/startlight", config.MethodPost) + assert.True(t, ok) + assert.NotNil(t, m) +} + func TestSearchWildcard(t *testing.T) { rt := &Tree{ tree: avltree.NewWithStringComparator(), From a90e51117467a30970bc052a543525045151ad94 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Thu, 24 Sep 2020 13:40:47 +0800 Subject: [PATCH 15/18] remove router group Signed-off-by: williamfeng323 --- pkg/router/route.go | 143 ++++++++++++++++++++++++------- pkg/router/route_test.go | 152 ++++++++++++++++++++++++--------- pkg/router/router_tree.go | 141 ------------------------------ pkg/router/router_tree_test.go | 135 ----------------------------- 4 files changed, 227 insertions(+), 344 deletions(-) delete mode 100644 pkg/router/router_tree.go delete mode 100644 pkg/router/router_tree_test.go diff --git a/pkg/router/route.go b/pkg/router/route.go index e372f5306..672f811c7 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -18,8 +18,9 @@ package router import ( - "path" + "fmt" "strings" + "sync" ) import ( @@ -28,45 +29,129 @@ import ( "github.com/pkg/errors" ) -// Group a easy way to manage the actual router tree, provides the apis to group the routers -type Group struct { - root bool - basePath string - routerTree *avltree.Tree +// Node defines the single method of the router configured API +type Node struct { + fullPath string + wildcard bool + methods map[config.HTTPVerb]*config.Method + lock sync.RWMutex } -// Group deviates a new router group from current group. use the same routerTree. -func (rg *Group) Group(relativePath string) (*Group, error) { - if len(relativePath) == 0 { - return nil, errors.New("Cannot group router with empty path") +// Route defines the tree of router APIs +type Route struct { + tree *avltree.Tree + wildcardTree *avltree.Tree +} + +// Put put a key val into the tree +func (rt *Route) Put(fullPath string, method config.Method) error { + fullPath = strings.ToLower(fullPath) + wildcard := containParam(fullPath) + + if wildcardNode, found := rt.searchWildcard(fullPath); found { + return putMethod(wildcardNode, method) + } + + node, ok := rt.tree.Get(fullPath) + if !ok { + ms := make(map[config.HTTPVerb]*config.Method) + rn := &Node{ + fullPath: fullPath, + methods: ms, + wildcard: wildcard, + } + rn.methods[method.HTTPVerb] = &method + if wildcard { + rt.wildcardTree.Put(fullPath, rn) + } + rt.tree.Put(fullPath, rn) + return nil + } + if _, ok := node.(*Node).methods[method.HTTPVerb]; ok { + return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) + } + node.(*Node).methods[method.HTTPVerb] = &method + return nil +} + +// UpdateMethod update the api method in the existing router node +func (rt *Route) UpdateMethod(fullPath string, verb config.HTTPVerb, method config.Method) error { + node, found := rt.findNode(fullPath) + if found { + if _, ok := node.methods[verb]; ok { + node.methods[verb] = &method + } + } + return nil +} + +func (rt *Route) findNode(fullPath string) (*Node, bool) { + fullPath = strings.ToLower(fullPath) + var n interface{} + var found bool + if n, found = rt.searchWildcard(fullPath); !found { + n, found = rt.tree.Get(fullPath) } - if relativePath[0] != '/' { - return nil, errors.New("Path must start with '/'") + return n.(*Node), found +} + +// FindMethod returns the api that meets the +func (rt *Route) FindMethod(fullPath string, httpverb config.HTTPVerb) (*config.Method, bool) { + if n, found := rt.findNode(fullPath); found { + method, ok := n.methods[httpverb] + return method, ok } - return &Group{ - basePath: rg.absolutePath(relativePath), - routerTree: rg.routerTree, - }, nil + return nil, false } -func (rg *Group) absolutePath(relativePath string) string { - if len(relativePath) == 0 { - return rg.basePath +func (rt *Route) searchWildcard(fullPath string) (*Node, bool) { + wildcardPaths := rt.wildcardTree.Keys() + for _, p := range wildcardPaths { + if wildcardMatch(p.(string), fullPath) { + n, ok := rt.wildcardTree.Get(p) + return n.(*Node), ok + } } - return strings.TrimRight(path.Join(rg.basePath, relativePath), "/") + return nil, false } -// Add adds the new router node to the group -func (rg *Group) Add(path string, method config.Method) error { - rg.routerTree.Put(path, method) +func putMethod(node *Node, method config.Method) error { + if _, ok := node.methods[method.HTTPVerb]; ok { + return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) + } + node.methods[method.HTTPVerb] = &method return nil } -// NewRouter returns a nil tree root router group -func NewRouter() *Group { - return &Group{ - root: true, - basePath: "/", - routerTree: avltree.NewWithStringComparator(), +func containParam(fullPath string) bool { + for _, s := range fullPath { + if s == ':' { + return true + } + } + return false +} + +func wildcardMatch(wildcardPath string, checkPath string) bool { + wildcardPath = strings.ToLower(wildcardPath) + checkPath = strings.ToLower(checkPath) + wPathSplit := strings.Split(wildcardPath[1:], "/") + cPathSplit := strings.Split(checkPath[1:], "/") + if len(wPathSplit) != len(cPathSplit) { + return false + } + for i, s := range wPathSplit { + if containParam(s) { + cPathSplit[i] = s + } + } + return strings.Join(wPathSplit, "/") == strings.Join(cPathSplit, "/") +} + +// NewRoute returns an empty router tree +func NewRoute() *Route { + return &Route{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), } } diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go index fe0766014..5c80703e4 100644 --- a/pkg/router/route_test.go +++ b/pkg/router/route_test.go @@ -23,6 +23,7 @@ import ( import ( "github.com/dubbogo/dubbo-go-proxy/pkg/config" + "github.com/emirpasic/gods/trees/avltree" "github.com/stretchr/testify/assert" ) @@ -37,60 +38,133 @@ func getMockMethod(verb config.HTTPVerb) config.Method { } } -func TestAbsolutePath(t *testing.T) { - rg := &Group{ - basePath: "/", +func TestPut(t *testing.T) { + rt := &Route{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), } - rst := rg.absolutePath("abc") - assert.Equal(t, rst, "/abc") + n0 := getMockMethod(config.MethodGet) + rt.Put("/", n0) + _, ok := rt.tree.Get("/") + assert.True(t, ok) - rst = rg.absolutePath("") - assert.Equal(t, rst, "/") + err := rt.Put("/", n0) + assert.Error(t, err, "Method GET already exists in path /") - rg = &Group{ - basePath: "/a", - } + n1 := getMockMethod(config.MethodPost) + err = rt.Put("/mock", n0) + assert.Nil(t, err) + err = rt.Put("/mock", n1) + assert.Nil(t, err) + mNode, ok := rt.tree.Get("/mock") + assert.True(t, ok) + assert.Equal(t, len(mNode.(*Node).methods), 2) - rst = rg.absolutePath("") - assert.Equal(t, rst, "/a") + err = rt.Put("/mock/test", n0) + assert.Nil(t, err) + _, ok = rt.tree.Get("/mock") + assert.True(t, ok) - rst = rg.absolutePath("b") - assert.Equal(t, rst, "/a/b") + rt.Put("/test/:id", n0) + tNode, ok := rt.tree.Get("/test/:id") + assert.True(t, ok) + assert.True(t, tNode.(*Node).wildcard) - rst = rg.absolutePath("/b/") - assert.Equal(t, rst, "/a/b") + err = rt.Put("/test/:id", n1) + assert.Nil(t, err) + err = rt.Put("/test/js", n0) + assert.Error(t, err, "/test/:id wildcard already exist so that cannot add path /test/js") - rst = rg.absolutePath("/:id") - assert.Equal(t, rst, "/a/:id") + err = rt.Put("/test/:id/mock", n0) + tNode, ok = rt.tree.Get("/test/:id/mock") + assert.True(t, ok) + assert.True(t, tNode.(*Node).wildcard) + assert.Nil(t, err) } -func TestGroup(t *testing.T) { - rg := &Group{ - basePath: "/", - root: true, +func TestFindMethod(t *testing.T) { + rt := &Route{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), } - rg1, err := rg.Group("") - assert.Nil(t, rg1) - assert.Error(t, err, "Cannot group router with empty path") + n0 := getMockMethod(config.MethodGet) + n1 := getMockMethod(config.MethodPost) + e := rt.Put("/theboys", n0) + assert.Nil(t, e) + e = rt.Put("/theboys/:id", n0) + assert.Nil(t, e) + e = rt.Put("/vought/:id/supe/:name", n1) + assert.Nil(t, e) + + m, ok := rt.FindMethod("/theboys", config.MethodGet) + assert.True(t, ok) + assert.NotNil(t, m) - rg2, err := rg.Group("/test") - assert.Equal(t, rg2.basePath, "/test") - assert.Nil(t, err) + m, ok = rt.FindMethod("/theboys", config.MethodPost) + assert.False(t, ok) + assert.Nil(t, m) + + m, ok = rt.FindMethod("/vought/123/supe/startlight", config.MethodPost) + assert.True(t, ok) + assert.NotNil(t, m) +} - rg3, err := rg2.Group("mock") - assert.Nil(t, rg3) - assert.Error(t, err, "Path must start with '/'") +func TestUpdateMethod(t *testing.T) { + m0 := getMockMethod(config.MethodGet) + m1 := getMockMethod(config.MethodGet) + m0.Version = "1.0.0" + m1.Version = "2.0.0" + + rt := NewRoute() + rt.Put("/marvel", m0) + m, _ := rt.FindMethod("/marvel", config.MethodGet) + assert.Equal(t, m.Version, "1.0.0") + rt.UpdateMethod("/marvel", config.MethodGet, m1) + m, ok := rt.FindMethod("/marvel", config.MethodGet) + assert.True(t, ok) + assert.Equal(t, m.Version, "2.0.0") - rg4, err := rg2.Group("/mock") - assert.Nil(t, err) - assert.Equal(t, rg4.basePath, "/test/mock") + rt.Put("/theboys/:id", m0) + m, _ = rt.FindMethod("/theBoys/12345", config.MethodGet) + assert.Equal(t, m.Version, "1.0.0") + rt.UpdateMethod("/theBoys/:id", config.MethodGet, m1) + m, ok = rt.FindMethod("/theBoys/12345", config.MethodGet) + assert.True(t, ok) + assert.Equal(t, m.Version, "2.0.0") } -func TestAdd(t *testing.T) { - rg := NewRouter() - rg.Add("/", getMockMethod(config.MethodGet)) - _, ok := rg.routerTree.Get("/") +func TestSearchWildcard(t *testing.T) { + rt := &Route{ + tree: avltree.NewWithStringComparator(), + wildcardTree: avltree.NewWithStringComparator(), + } + n0 := getMockMethod(config.MethodGet) + e := rt.Put("/theboys", n0) + assert.Nil(t, e) + e = rt.Put("/theboys/:id", n0) + assert.Nil(t, e) + e = rt.Put("/vought/:id/supe/:name", n0) + assert.Nil(t, e) + + _, ok := rt.searchWildcard("/marvel") + assert.False(t, ok) + _, ok = rt.searchWildcard("/theboys/:id/age") + assert.False(t, ok) + _, ok = rt.searchWildcard("/theboys/butcher") + assert.True(t, ok) + _, ok = rt.searchWildcard("/vought/:id/supe/homelander") assert.True(t, ok) +} + +func TestContainParam(t *testing.T) { + assert.True(t, containParam("/test/:id")) + assert.False(t, containParam("/test")) + assert.True(t, containParam("/test/:id/mock")) +} - // rg.Add("/") +func TestWildcardMatch(t *testing.T) { + assert.True(t, wildcardMatch("/vought/:id", "/vought/12345")) + assert.True(t, wildcardMatch("/vought/:id", "/vought/125abc")) + assert.False(t, wildcardMatch("/vought/:id", "/vought/1234abcd/status")) + assert.True(t, wildcardMatch("/voughT/:id/:action", "/Vought/1234abcd/attack")) } diff --git a/pkg/router/router_tree.go b/pkg/router/router_tree.go deleted file mode 100644 index aff531dfe..000000000 --- a/pkg/router/router_tree.go +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 ( - "fmt" - "strings" - "sync" -) - -import ( - "github.com/dubbogo/dubbo-go-proxy/pkg/config" - "github.com/emirpasic/gods/trees/avltree" - "github.com/pkg/errors" -) - -// Node defines the single method of the router configured API -type Node struct { - fullPath string - wildcard bool - methods map[config.HTTPVerb]*config.Method - lock sync.RWMutex -} - -// Tree defines the tree of router APIs -type Tree struct { - tree *avltree.Tree - wildcardTree *avltree.Tree -} - -// Put put a key val into the tree -func (rt *Tree) Put(fullPath string, method config.Method) error { - fullPath = strings.ToLower(fullPath) - wildcard := containParam(fullPath) - - if wildcardNode, found := rt.searchWildcard(fullPath); found { - return putMethod(wildcardNode, method) - } - - node, ok := rt.tree.Get(fullPath) - if !ok { - ms := make(map[config.HTTPVerb]*config.Method) - rn := &Node{ - fullPath: fullPath, - methods: ms, - wildcard: wildcard, - } - rn.methods[method.HTTPVerb] = &method - if wildcard { - rt.wildcardTree.Put(fullPath, rn) - } - rt.tree.Put(fullPath, rn) - return nil - } - if _, ok := node.(*Node).methods[method.HTTPVerb]; ok { - return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) - } - node.(*Node).methods[method.HTTPVerb] = &method - return nil -} - -// FindMethod returns the api that meets the -func (rt *Tree) FindMethod(fullPath string, method config.HTTPVerb) (*config.Method, bool) { - var n interface{} - var found bool - if n, found = rt.searchWildcard(fullPath); !found { - n, found = rt.tree.Get(fullPath) - } - if found { - method, ok := n.(*Node).methods[method] - return method, ok - } - return nil, false -} - -func (rt *Tree) searchWildcard(fullPath string) (*Node, bool) { - wildcardPaths := rt.wildcardTree.Keys() - for _, p := range wildcardPaths { - if wildcardMatch(p.(string), fullPath) { - n, ok := rt.wildcardTree.Get(p) - return n.(*Node), ok - } - } - return nil, false -} - -func putMethod(node *Node, method config.Method) error { - if _, ok := node.methods[method.HTTPVerb]; ok { - return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) - } - node.methods[method.HTTPVerb] = &method - return nil -} - -func containParam(fullPath string) bool { - for _, s := range fullPath { - if s == ':' { - return true - } - } - return false -} - -func wildcardMatch(wildcardPath string, checkPath string) bool { - wildcardPath = strings.ToLower(wildcardPath) - checkPath = strings.ToLower(checkPath) - wPathSplit := strings.Split(wildcardPath[1:], "/") - cPathSplit := strings.Split(checkPath[1:], "/") - if len(wPathSplit) != len(cPathSplit) { - return false - } - for i, s := range wPathSplit { - if containParam(s) { - cPathSplit[i] = s - } - } - return strings.Join(wPathSplit, "/") == strings.Join(cPathSplit, "/") -} - -// NewTree returns an empty router tree -func NewTree() *Tree { - return &Tree{ - tree: avltree.NewWithStringComparator(), - wildcardTree: avltree.NewWithStringComparator(), - } -} diff --git a/pkg/router/router_tree_test.go b/pkg/router/router_tree_test.go deleted file mode 100644 index 9221fed49..000000000 --- a/pkg/router/router_tree_test.go +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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 ( - "testing" -) - -import ( - "github.com/dubbogo/dubbo-go-proxy/pkg/config" - "github.com/emirpasic/gods/trees/avltree" - "github.com/stretchr/testify/assert" -) - -func TestPut(t *testing.T) { - rt := &Tree{ - tree: avltree.NewWithStringComparator(), - wildcardTree: avltree.NewWithStringComparator(), - } - n0 := getMockMethod(config.MethodGet) - rt.Put("/", n0) - _, ok := rt.tree.Get("/") - assert.True(t, ok) - - err := rt.Put("/", n0) - assert.Error(t, err, "Method GET already exists in path /") - - n1 := getMockMethod(config.MethodPost) - err = rt.Put("/mock", n0) - assert.Nil(t, err) - err = rt.Put("/mock", n1) - assert.Nil(t, err) - mNode, ok := rt.tree.Get("/mock") - assert.True(t, ok) - assert.Equal(t, len(mNode.(*Node).methods), 2) - - err = rt.Put("/mock/test", n0) - assert.Nil(t, err) - _, ok = rt.tree.Get("/mock") - assert.True(t, ok) - - rt.Put("/test/:id", n0) - tNode, ok := rt.tree.Get("/test/:id") - assert.True(t, ok) - assert.True(t, tNode.(*Node).wildcard) - - err = rt.Put("/test/:id", n1) - assert.Nil(t, err) - err = rt.Put("/test/js", n0) - assert.Error(t, err, "/test/:id wildcard already exist so that cannot add path /test/js") - - err = rt.Put("/test/:id/mock", n0) - tNode, ok = rt.tree.Get("/test/:id/mock") - assert.True(t, ok) - assert.True(t, tNode.(*Node).wildcard) - assert.Nil(t, err) -} - -func TestFindMethod(t *testing.T) { - rt := &Tree{ - tree: avltree.NewWithStringComparator(), - wildcardTree: avltree.NewWithStringComparator(), - } - n0 := getMockMethod(config.MethodGet) - n1 := getMockMethod(config.MethodPost) - e := rt.Put("/theboys", n0) - assert.Nil(t, e) - e = rt.Put("/theboys/:id", n0) - assert.Nil(t, e) - e = rt.Put("/vought/:id/supe/:name", n1) - assert.Nil(t, e) - - m, ok := rt.FindMethod("/theboys", config.MethodGet) - assert.True(t, ok) - assert.NotNil(t, m) - - m, ok = rt.FindMethod("/theboys", config.MethodPost) - assert.False(t, ok) - assert.Nil(t, m) - - m, ok = rt.FindMethod("/vought/123/supe/startlight", config.MethodPost) - assert.True(t, ok) - assert.NotNil(t, m) -} - -func TestSearchWildcard(t *testing.T) { - rt := &Tree{ - tree: avltree.NewWithStringComparator(), - wildcardTree: avltree.NewWithStringComparator(), - } - n0 := getMockMethod(config.MethodGet) - e := rt.Put("/theboys", n0) - assert.Nil(t, e) - e = rt.Put("/theboys/:id", n0) - assert.Nil(t, e) - e = rt.Put("/vought/:id/supe/:name", n0) - assert.Nil(t, e) - - _, ok := rt.searchWildcard("/marvel") - assert.False(t, ok) - _, ok = rt.searchWildcard("/theboys/:id/age") - assert.False(t, ok) - _, ok = rt.searchWildcard("/theboys/butcher") - assert.True(t, ok) - _, ok = rt.searchWildcard("/vought/:id/supe/homelander") - assert.True(t, ok) -} - -func TestContainParam(t *testing.T) { - assert.True(t, containParam("/test/:id")) - assert.False(t, containParam("/test")) - assert.True(t, containParam("/test/:id/mock")) -} - -func TestWildcardMatch(t *testing.T) { - assert.True(t, wildcardMatch("/vought/:id", "/vought/12345")) - assert.True(t, wildcardMatch("/vought/:id", "/vought/125abc")) - assert.False(t, wildcardMatch("/vought/:id", "/vought/1234abcd/status")) - assert.True(t, wildcardMatch("/voughT/:id/:action", "/Vought/1234abcd/attack")) -} From abf403ff66ee0310cba6bb9eba175187c4753864 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Fri, 25 Sep 2020 00:29:25 +0800 Subject: [PATCH 16/18] added lock to the tree update Signed-off-by: williamfeng323 --- pkg/router/route.go | 17 ++++++++++------- pkg/router/route_test.go | 5 ++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/router/route.go b/pkg/router/route.go index 672f811c7..13c9d6acd 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -24,27 +24,32 @@ import ( ) import ( - "github.com/dubbogo/dubbo-go-proxy/pkg/config" "github.com/emirpasic/gods/trees/avltree" "github.com/pkg/errors" ) +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" +) + // Node defines the single method of the router configured API type Node struct { fullPath string wildcard bool methods map[config.HTTPVerb]*config.Method - lock sync.RWMutex } // Route defines the tree of router APIs type Route struct { + lock sync.RWMutex tree *avltree.Tree wildcardTree *avltree.Tree } // Put put a key val into the tree func (rt *Route) Put(fullPath string, method config.Method) error { + rt.lock.Lock() + defer rt.lock.Unlock() fullPath = strings.ToLower(fullPath) wildcard := containParam(fullPath) @@ -67,15 +72,13 @@ func (rt *Route) Put(fullPath string, method config.Method) error { rt.tree.Put(fullPath, rn) return nil } - if _, ok := node.(*Node).methods[method.HTTPVerb]; ok { - return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, fullPath)) - } - node.(*Node).methods[method.HTTPVerb] = &method - return nil + return putMethod(node.(*Node), method) } // UpdateMethod update the api method in the existing router node func (rt *Route) UpdateMethod(fullPath string, verb config.HTTPVerb, method config.Method) error { + rt.lock.Lock() + defer rt.lock.Unlock() node, found := rt.findNode(fullPath) if found { if _, ok := node.methods[verb]; ok { diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go index 5c80703e4..b909ef03d 100644 --- a/pkg/router/route_test.go +++ b/pkg/router/route_test.go @@ -22,11 +22,14 @@ import ( ) import ( - "github.com/dubbogo/dubbo-go-proxy/pkg/config" "github.com/emirpasic/gods/trees/avltree" "github.com/stretchr/testify/assert" ) +import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/config" +) + func getMockMethod(verb config.HTTPVerb) config.Method { inbound := config.InboundRequest{} integration := config.IntegrationRequest{} From 3a877ae97dcd9361efa910eb28a1509e37d46937 Mon Sep 17 00:00:00 2001 From: williamfeng323 Date: Sat, 26 Sep 2020 17:25:53 +0800 Subject: [PATCH 17/18] use constant value to represent : and /; added back filter to resource level Signed-off-by: williamfeng323 --- configs/api_config.yaml | 10 ++++++---- pkg/common/constant/http.go | 3 +++ pkg/config/api_config.go | 1 + pkg/config/mock/api_config.yml | 2 ++ pkg/router/route.go | 9 +++++---- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/configs/api_config.yaml b/configs/api_config.yaml index b41c38089..2cf1faee8 100644 --- a/configs/api_config.yaml +++ b/configs/api_config.yaml @@ -5,13 +5,15 @@ resources: type: restful description: resource documentation filters: - - filterA - - filterB + - filter0 methods: - httpVerb: GET + filters: + - filterA + - filterB onAir: true inboundRequest: - type: http + requestType: http headers: - name: auth required: true @@ -23,7 +25,7 @@ resources: requestBody: - definitionName: modelDefinition integrationRequest: - type: dubbo + requestType: dubbo mappingParams: - name: queryStrings.id mapTo: 1 diff --git a/pkg/common/constant/http.go b/pkg/common/constant/http.go index d831ce8a6..f71883d8d 100644 --- a/pkg/common/constant/http.go +++ b/pkg/common/constant/http.go @@ -24,6 +24,9 @@ const ( HeaderValueJsonUtf8 = "application/json;charset=UTF-8" HeaderValueTextPlain = "text/plain" HeaderValueAll = "*" + + PathSlash = "/" + PathParamIdentifier = ':' ) const ( diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 3c9126d4e..90b29c112 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -62,6 +62,7 @@ type Resource struct { Type string `yaml:"type"` // Restful, Dubbo Path string `yaml:"path"` Description string `yaml:"description"` + Filters []string `yaml:"filters"` Methods []Method `yaml:"methods"` Resources []Resource `yaml:"resources,omitempty"` } diff --git a/pkg/config/mock/api_config.yml b/pkg/config/mock/api_config.yml index 632f49f12..2cf1faee8 100644 --- a/pkg/config/mock/api_config.yml +++ b/pkg/config/mock/api_config.yml @@ -4,6 +4,8 @@ resources: - path: '/' type: restful description: resource documentation + filters: + - filter0 methods: - httpVerb: GET filters: diff --git a/pkg/router/route.go b/pkg/router/route.go index 13c9d6acd..18aad741b 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -29,6 +29,7 @@ import ( ) import ( + "github.com/dubbogo/dubbo-go-proxy/pkg/common/constant" "github.com/dubbogo/dubbo-go-proxy/pkg/config" ) @@ -128,7 +129,7 @@ func putMethod(node *Node, method config.Method) error { func containParam(fullPath string) bool { for _, s := range fullPath { - if s == ':' { + if s == constant.PathParamIdentifier { return true } } @@ -138,8 +139,8 @@ func containParam(fullPath string) bool { func wildcardMatch(wildcardPath string, checkPath string) bool { wildcardPath = strings.ToLower(wildcardPath) checkPath = strings.ToLower(checkPath) - wPathSplit := strings.Split(wildcardPath[1:], "/") - cPathSplit := strings.Split(checkPath[1:], "/") + wPathSplit := strings.Split(wildcardPath[1:], constant.PathSlash) + cPathSplit := strings.Split(checkPath[1:], constant.PathSlash) if len(wPathSplit) != len(cPathSplit) { return false } @@ -148,7 +149,7 @@ func wildcardMatch(wildcardPath string, checkPath string) bool { cPathSplit[i] = s } } - return strings.Join(wPathSplit, "/") == strings.Join(cPathSplit, "/") + return strings.Join(wPathSplit, constant.PathSlash) == strings.Join(cPathSplit, constant.PathSlash) } // NewRoute returns an empty router tree From 2e44ebe7cce0165b7a2a25810e6c7d9363126315 Mon Sep 17 00:00:00 2001 From: william feng Date: Thu, 1 Oct 2020 02:09:23 +0800 Subject: [PATCH 18/18] fix for the comments Signed-off-by: williamfeng323 --- pkg/common/constant/http.go | 2 +- pkg/config/api_config.go | 2 +- pkg/router/route.go | 70 +++++++++++++++++-------------------- pkg/router/route_test.go | 6 ---- 4 files changed, 35 insertions(+), 45 deletions(-) diff --git a/pkg/common/constant/http.go b/pkg/common/constant/http.go index f71883d8d..e250d5eb2 100644 --- a/pkg/common/constant/http.go +++ b/pkg/common/constant/http.go @@ -26,7 +26,7 @@ const ( HeaderValueAll = "*" PathSlash = "/" - PathParamIdentifier = ':' + PathParamIdentifier = ":" ) const ( diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 90b29c112..d191397e3 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -69,7 +69,7 @@ type Resource struct { // Method defines the method of the api type Method struct { - OnAir bool `yaml:"onAir"` + OnAir bool `yaml:"onAir"` // true means the method is up and false means method is down Filters []string `yaml:"filters"` HTTPVerb `yaml:"httpVerb"` InboundRequest `yaml:"inboundRequest"` diff --git a/pkg/router/route.go b/pkg/router/route.go index 18aad741b..27c9f13ae 100644 --- a/pkg/router/route.go +++ b/pkg/router/route.go @@ -18,7 +18,6 @@ package router import ( - "fmt" "strings" "sync" ) @@ -49,40 +48,33 @@ type Route struct { // Put put a key val into the tree func (rt *Route) Put(fullPath string, method config.Method) error { + lowerCasePath := strings.ToLower(fullPath) + node, ok := rt.findNode(lowerCasePath) rt.lock.Lock() defer rt.lock.Unlock() - fullPath = strings.ToLower(fullPath) - wildcard := containParam(fullPath) - - if wildcardNode, found := rt.searchWildcard(fullPath); found { - return putMethod(wildcardNode, method) - } - - node, ok := rt.tree.Get(fullPath) if !ok { - ms := make(map[config.HTTPVerb]*config.Method) + wildcard := strings.Contains(lowerCasePath, constant.PathParamIdentifier) rn := &Node{ - fullPath: fullPath, - methods: ms, + fullPath: lowerCasePath, + methods: map[config.HTTPVerb]*config.Method{method.HTTPVerb: &method}, wildcard: wildcard, } - rn.methods[method.HTTPVerb] = &method if wildcard { - rt.wildcardTree.Put(fullPath, rn) + rt.wildcardTree.Put(lowerCasePath, rn) } - rt.tree.Put(fullPath, rn) + rt.tree.Put(lowerCasePath, rn) return nil } - return putMethod(node.(*Node), method) + return node.putMethod(method) } // UpdateMethod update the api method in the existing router node func (rt *Route) UpdateMethod(fullPath string, verb config.HTTPVerb, method config.Method) error { - rt.lock.Lock() - defer rt.lock.Unlock() node, found := rt.findNode(fullPath) if found { if _, ok := node.methods[verb]; ok { + rt.lock.Lock() + defer rt.lock.Unlock() node.methods[verb] = &method } } @@ -90,11 +82,15 @@ func (rt *Route) UpdateMethod(fullPath string, verb config.HTTPVerb, method conf } func (rt *Route) findNode(fullPath string) (*Node, bool) { - fullPath = strings.ToLower(fullPath) + lowerPath := strings.ToLower(fullPath) var n interface{} var found bool - if n, found = rt.searchWildcard(fullPath); !found { - n, found = rt.tree.Get(fullPath) + if n, found = rt.searchWildcard(lowerPath); !found { + rt.lock.RLock() + defer rt.lock.RUnlock() + if n, found = rt.tree.Get(lowerPath); !found { + return nil, false + } } return n.(*Node), found } @@ -102,6 +98,8 @@ func (rt *Route) findNode(fullPath string) (*Node, bool) { // FindMethod returns the api that meets the func (rt *Route) FindMethod(fullPath string, httpverb config.HTTPVerb) (*config.Method, bool) { if n, found := rt.findNode(fullPath); found { + rt.lock.RLock() + defer rt.lock.RUnlock() method, ok := n.methods[httpverb] return method, ok } @@ -109,6 +107,8 @@ func (rt *Route) FindMethod(fullPath string, httpverb config.HTTPVerb) (*config. } func (rt *Route) searchWildcard(fullPath string) (*Node, bool) { + rt.lock.RLock() + defer rt.lock.RUnlock() wildcardPaths := rt.wildcardTree.Keys() for _, p := range wildcardPaths { if wildcardMatch(p.(string), fullPath) { @@ -119,34 +119,30 @@ func (rt *Route) searchWildcard(fullPath string) (*Node, bool) { return nil, false } -func putMethod(node *Node, method config.Method) error { +func (node *Node) putMethod(method config.Method) error { if _, ok := node.methods[method.HTTPVerb]; ok { - return errors.New(fmt.Sprintf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath)) + return errors.Errorf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath) } node.methods[method.HTTPVerb] = &method return nil } -func containParam(fullPath string) bool { - for _, s := range fullPath { - if s == constant.PathParamIdentifier { - return true - } - } - return false -} - +// 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 { - wildcardPath = strings.ToLower(wildcardPath) - checkPath = strings.ToLower(checkPath) - wPathSplit := strings.Split(wildcardPath[1:], constant.PathSlash) - cPathSplit := strings.Split(checkPath[1:], constant.PathSlash) + 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 } for i, s := range wPathSplit { - if containParam(s) { + if strings.Contains(s, constant.PathParamIdentifier) { cPathSplit[i] = s + } else if wPathSplit[i] != cPathSplit[i] { + return false } } return strings.Join(wPathSplit, constant.PathSlash) == strings.Join(cPathSplit, constant.PathSlash) diff --git a/pkg/router/route_test.go b/pkg/router/route_test.go index b909ef03d..b7c22bdfa 100644 --- a/pkg/router/route_test.go +++ b/pkg/router/route_test.go @@ -159,12 +159,6 @@ func TestSearchWildcard(t *testing.T) { assert.True(t, ok) } -func TestContainParam(t *testing.T) { - assert.True(t, containParam("/test/:id")) - assert.False(t, containParam("/test")) - assert.True(t, containParam("/test/:id/mock")) -} - func TestWildcardMatch(t *testing.T) { assert.True(t, wildcardMatch("/vought/:id", "/vought/12345")) assert.True(t, wildcardMatch("/vought/:id", "/vought/125abc"))