You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by zt...@apache.org on 2021/11/25 03:53:39 UTC
[dubbo-go-pixiu] branch develop updated: add trie for route (#262)
This is an automated email from the ASF dual-hosted git repository.
ztelur pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go-pixiu.git
The following commit(s) were added to refs/heads/develop by this push:
new 11535b0 add trie for route (#262)
11535b0 is described below
commit 11535b0fd11ac4d5f676148dc0ad8e64e730757e
Author: yqxu <31...@qq.com>
AuthorDate: Thu Nov 25 11:53:35 2021 +0800
add trie for route (#262)
* add trie for route
* bug fix
* bug fix
* merge master
* add assert for params
* add assert for params
* a/b should match prefix:a/b
* a/b should match prefix:a/b
* a/b should match prefix:a/b
* a/b should match prefix:a/b
* a/b should match prefix:a/b
* a/b should match prefix:a/b
* merge with master
* merge with master
* merge with master
* merge with master
* merge with master
* merge with master
* merge with master
* review dog
* review dog
* review dog
* review dog
* review dog
* add note
* add note
* add note
* add note
* remove chinese
* update
* remove chinese and fix reviewdog issues
Co-authored-by: yangqing.xyq <ya...@gongdao.com>
Co-authored-by: Mark4z <36...@users.noreply.github.com>
Co-authored-by: zhangxun <18...@163.com>
Co-authored-by: Randy <zt...@gmail.com>
---
pkg/adapter/dubboregistry/registry/registry.go | 2 -
pkg/common/constant/http.go | 7 +
pkg/common/http/manager_test.go | 28 +-
pkg/common/router/router.go | 65 +++--
pkg/common/router/router_test.go | 22 +-
pkg/common/router/trie/trie.go | 348 +++++++++++++++++++++++++
pkg/common/router/trie/trie_test.go | 133 ++++++++++
pkg/common/util/stringutil/stringutil.go | 54 ++++
pkg/config/conf_test.yaml | 6 -
pkg/config/config_load.go | 1 +
pkg/config/config_load_test.go | 9 +-
pkg/context/http/util.go | 62 -----
pkg/model/router.go | 101 ++-----
pkg/model/router_test.go | 267 -------------------
pkg/server/http.go | 14 +-
samples/http/simple/server/app/server.go | 4 +-
16 files changed, 627 insertions(+), 496 deletions(-)
diff --git a/pkg/adapter/dubboregistry/registry/registry.go b/pkg/adapter/dubboregistry/registry/registry.go
index ca41d15..cf02afc 100644
--- a/pkg/adapter/dubboregistry/registry/registry.go
+++ b/pkg/adapter/dubboregistry/registry/registry.go
@@ -148,9 +148,7 @@ func GetRouter() model.Router {
Match: model.RouterMatch{
Prefix: "",
Path: "",
- Regex: "",
Methods: nil,
- Headers: nil,
},
}
}
diff --git a/pkg/common/constant/http.go b/pkg/common/constant/http.go
index 2aa59cf..0a488bc 100644
--- a/pkg/common/constant/http.go
+++ b/pkg/common/constant/http.go
@@ -43,3 +43,10 @@ const (
PprofDefaultAddress = "0.0.0.0"
PprofDefaultPort = 7070
)
+
+const (
+ Get = "GET"
+ Put = "PUT"
+ Post = "POST"
+ Delete = "DELETE"
+)
diff --git a/pkg/common/http/manager_test.go b/pkg/common/http/manager_test.go
index f06acb0..38b9ab3 100644
--- a/pkg/common/http/manager_test.go
+++ b/pkg/common/http/manager_test.go
@@ -30,6 +30,7 @@ import (
import (
"github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
+ "github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
contexthttp "github.com/apache/dubbo-go-pixiu/pkg/context/http"
"github.com/apache/dubbo-go-pixiu/pkg/context/mock"
"github.com/apache/dubbo-go-pixiu/pkg/logger"
@@ -91,29 +92,10 @@ func TestCreateHttpConnectionManager(t *testing.T) {
hcmc := model.HttpConnectionManagerConfig{
RouteConfig: model.RouteConfiguration{
- Routes: []*model.Router{
- {
- ID: "1",
- Match: model.RouterMatch{
- Prefix: "/api/v1",
- Methods: []string{
- "POST",
- },
- Path: "",
- Regex: "",
- Headers: []model.HeaderMatcher{
- {Name: "X-Dgp-Way",
- Values: []string{"Dubbo"},
- Regex: false,
- },
- },
- },
- Route: model.RouteAction{
- Cluster: "test_dubbo",
- ClusterNotFoundResponseCode: 505,
- },
- },
- },
+ RouteTrie: trie.NewTrieWithDefault("POST/api/v1/**", model.RouteAction{
+ Cluster: "test_dubbo",
+ ClusterNotFoundResponseCode: 505,
+ }),
Dynamic: false,
},
HTTPFilters: []*model.HTTPFilter{
diff --git a/pkg/common/router/router.go b/pkg/common/router/router.go
index 856a768..ddbd3ce 100644
--- a/pkg/common/router/router.go
+++ b/pkg/common/router/router.go
@@ -18,10 +18,14 @@
package router
import (
+ "strings"
"sync"
)
import (
+ "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+ "github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
+ "github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
"github.com/apache/dubbo-go-pixiu/pkg/context/http"
"github.com/apache/dubbo-go-pixiu/pkg/model"
"github.com/apache/dubbo-go-pixiu/pkg/server"
@@ -31,18 +35,17 @@ type (
// RouterCoordinator the router coordinator for http connection manager
RouterCoordinator struct {
activeConfig *model.RouteConfiguration
-
- rw sync.RWMutex
+ rw sync.RWMutex
}
)
// CreateRouterCoordinator create coordinator for http connection manager
func CreateRouterCoordinator(hcmc *model.HttpConnectionManagerConfig) *RouterCoordinator {
-
rc := &RouterCoordinator{activeConfig: &hcmc.RouteConfig}
if hcmc.RouteConfig.Dynamic {
server.GetRouterManager().AddRouterListener(rc)
}
+ rc.initTrie()
return rc
}
@@ -54,31 +57,61 @@ func (rm *RouterCoordinator) Route(hc *http.HttpContext) (*model.RouteAction, er
return rm.activeConfig.Route(hc.Request)
}
+func getTrieKey(method string, path string, isPrefix bool) string {
+ if isPrefix {
+ if !strings.HasSuffix(path, constant.PathSlash) {
+ path = path + constant.PathSlash
+ }
+ path = path + "**"
+ }
+ return stringutil.GetTrieKey(method, path)
+}
+
+func (rm *RouterCoordinator) initTrie() {
+ if rm.activeConfig.RouteTrie.IsEmpty() {
+ rm.activeConfig.RouteTrie = trie.NewTrie()
+ }
+ for _, router := range rm.activeConfig.Routes {
+ rm.OnAddRouter(router)
+ }
+}
+
// OnAddRouter add router
func (rm *RouterCoordinator) OnAddRouter(r *model.Router) {
+ //TODO: lock move to trie node
rm.rw.Lock()
defer rm.rw.Unlock()
-
- for _, old := range rm.activeConfig.Routes {
- if old.ID == r.ID {
- old.Route = r.Route
- old.Match = r.Match
- return
+ if r.Match.Methods == nil {
+ r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post}
+ }
+ isPrefix := r.Match.Prefix != ""
+ for _, method := range r.Match.Methods {
+ var key string
+ if isPrefix {
+ key = getTrieKey(method, r.Match.Prefix, isPrefix)
+ } else {
+ key = getTrieKey(method, r.Match.Path, isPrefix)
}
+ _, _ = rm.activeConfig.RouteTrie.Put(key, r.Route)
}
-
- rm.activeConfig.Routes = append(rm.activeConfig.Routes, r)
}
// OnDeleteRouter delete router
-func (rm *RouterCoordinator) OnDeleteRouter(new *model.Router) {
+func (rm *RouterCoordinator) OnDeleteRouter(r *model.Router) {
rm.rw.Lock()
defer rm.rw.Unlock()
- for i, r := range rm.activeConfig.Routes {
- if r.ID == new.ID {
- rm.activeConfig.Routes = append(rm.activeConfig.Routes[:i], rm.activeConfig.Routes[i+1:]...)
- break
+ if r.Match.Methods == nil {
+ r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post}
+ }
+ isPrefix := r.Match.Prefix != ""
+ for _, method := range r.Match.Methods {
+ var key string
+ if isPrefix {
+ key = getTrieKey(method, r.Match.Prefix, isPrefix)
+ } else {
+ key = getTrieKey(method, r.Match.Path, isPrefix)
}
+ _, _ = rm.activeConfig.RouteTrie.Remove(key)
}
}
diff --git a/pkg/common/router/router_test.go b/pkg/common/router/router_test.go
index 6a14980..fb3e42f 100644
--- a/pkg/common/router/router_test.go
+++ b/pkg/common/router/router_test.go
@@ -19,6 +19,7 @@ package router
import (
"bytes"
+ "github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
"net/http"
"testing"
)
@@ -35,23 +36,10 @@ import (
func TestCreateRouterCoordinator(t *testing.T) {
hcmc := model.HttpConnectionManagerConfig{
RouteConfig: model.RouteConfiguration{
- Routes: []*model.Router{
- {
- ID: "1",
- Match: model.RouterMatch{
- Prefix: "/api/v1",
- Methods: []string{
- "POST",
- },
- Path: "",
- Regex: "",
- },
- Route: model.RouteAction{
- Cluster: "test_dubbo",
- ClusterNotFoundResponseCode: 505,
- },
- },
- },
+ RouteTrie: trie.NewTrieWithDefault("POST/api/v1/**", model.RouteAction{
+ Cluster: "test_dubbo",
+ ClusterNotFoundResponseCode: 505,
+ }),
Dynamic: false,
},
HTTPFilters: []*model.HTTPFilter{
diff --git a/pkg/common/router/trie/trie.go b/pkg/common/router/trie/trie.go
new file mode 100644
index 0000000..d34b84d
--- /dev/null
+++ b/pkg/common/router/trie/trie.go
@@ -0,0 +1,348 @@
+/*
+ * 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 trie
+
+import (
+ "github.com/pkg/errors"
+)
+
+import (
+ "github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
+ "github.com/apache/dubbo-go-pixiu/pkg/logger"
+)
+
+// Trie
+type Trie struct {
+ root Node
+}
+
+// NewTrie
+func NewTrie() Trie {
+ return Trie{root: Node{endOfPath: false, matchStr: ""}}
+}
+
+// NewTrieWithDefault
+func NewTrieWithDefault(path string, defVal interface{}) Trie {
+ ret := Trie{root: Node{endOfPath: false, matchStr: ""}}
+ _, _ = ret.Put(path, defVal)
+ return ret
+}
+
+// Node
+type Node struct {
+ matchStr string //abc match abc, :a match all words as a variable names a , * match all words ,** match all words and children.
+ children map[string]*Node // in path /a/b/c , b is child of a , c is child of b
+ PathVariablesSet map[string]*Node // in path /:a/b/c/:d , :a is a path variable node of level1 , :d is path variable node of level4
+ PathVariableNode *Node // in path /:a/b/c/:d , /b/c/:d is a child tree of pathVariable node :a ,and some special logic for match pathVariable it better not store in children.
+ MatchAllNode *Node // /a/b/** /** is a match all Node.
+ endOfPath bool // if true means a real path exists , /a/b/c/d only node of d is true, a,b,c is false.
+ bizInfo interface{} // route info and any other info store here.
+}
+
+//IsEmpty put key and values into trie as map.
+func (trie *Trie) IsEmpty() bool {
+ return trie.root.IsEmpty()
+}
+
+//Put put key and values into trie as map.
+func (trie *Trie) Put(withOutHost string, bizInfo interface{}) (bool, error) {
+ if bizInfo == nil {
+ return false, errors.Errorf("data to put should not be nil.")
+ }
+ parts := stringutil.Split(withOutHost)
+ return trie.root.internalPut(parts, bizInfo)
+}
+
+//Put put key and values into trie as map.
+func (trie *Trie) PutOrUpdate(withOutHost string, bizInfo interface{}) (bool, error) {
+ if bizInfo == nil {
+ return false, errors.Errorf("data to put should not be nil.")
+ }
+ parts := stringutil.Split(withOutHost)
+ if _, err := trie.Remove(withOutHost); err != nil {
+ logger.Infof("PutOrUpdate function remote (withOutHost{%s}) = error{%v}", withOutHost, err)
+ }
+ //if n != nil {
+ // //TODO: log n.bizInfo for trouble shooting
+ //}
+ return trie.root.internalPut(parts, bizInfo)
+}
+
+//Get get values according key.pathVariable not supported.
+func (trie Trie) Get(withOutHost string) (*Node, []string, bool, error) {
+ parts := stringutil.Split(withOutHost)
+ node, param, ok, e := trie.root.Get(parts)
+ length := len(param)
+ for i := 0; i < length/2; i++ {
+ temp := param[length-1-i]
+ param[length-1-i] = param[i]
+ param[i] = temp
+ }
+ return node, param, ok, e
+}
+
+//Match get values according url , pathVariable supported.
+// returns:
+//*Node this node in path, if not exists return nil
+//[]string reversed array of pathVariable value /valA/valB/valC matchs /:aa/:bb/:cc returns array of (valC,valB,valA)
+//bool is ok
+//error
+func (trie Trie) Match(withOutHost string) (*Node, []string, bool) {
+ parts := stringutil.Split(withOutHost)
+ node, param, ok := trie.root.Match(parts)
+ length := len(param)
+ for i := 0; i < length/2; i++ {
+ temp := param[length-1-i]
+ param[length-1-i] = param[i]
+ param[i] = temp
+ }
+ return node, param, ok
+}
+
+//Remove remove key and value from trie. logic delete can't release memory, rebuild if necessary when lake of memory.
+func (trie Trie) Remove(withOutHost string) (*Node, error) {
+ n, _, _, e := trie.Get(withOutHost)
+ if e != nil {
+ return nil, e
+ }
+ if n != nil {
+ n.endOfPath = false
+ }
+ return n, nil
+}
+
+//Contains return if key exists in trie
+func (trie Trie) Contains(withOutHost string) (bool, error) {
+ parts := stringutil.Split(withOutHost)
+ ret, _, _, e := trie.root.Get(parts)
+ if e != nil {
+ return true, e
+ }
+ return !(ret == nil), nil
+}
+
+//Put node put
+func (node *Node) internalPut(keys []string, bizInfo interface{}) (bool, error) {
+ // empty node initialization
+ if node.children == nil {
+ node.children = map[string]*Node{}
+ }
+ if len(keys) == 0 {
+ return true, nil
+ }
+
+ key := keys[0]
+ // isReal is the end of url path, means node is a place of url end,so the path with parentNode has a real url exists.
+ isReal := len(keys) == 1
+ isSuccess := node.put(key, isReal, bizInfo)
+
+ if !isSuccess {
+ return false, nil
+ }
+ childKeys := keys[1:]
+
+ if stringutil.IsPathVariableOrWildcard(key) {
+ return node.PathVariableNode.internalPut(childKeys, bizInfo)
+ } else if stringutil.IsMatchAll(key) {
+ return isSuccess, nil
+ } else {
+ return node.children[key].internalPut(childKeys, bizInfo)
+ }
+
+}
+
+//IsEmpty return true if empty
+func (node *Node) IsEmpty() bool {
+ if node.children == nil && node.matchStr == "" && node.PathVariableNode == nil && node.PathVariablesSet == nil {
+ return true
+ }
+ return false
+}
+
+//GetBizInfo get info
+func (node *Node) GetBizInfo() interface{} {
+ return node.bizInfo
+}
+
+//Match node match
+
+func (node *Node) Match(parts []string) (*Node, []string, bool) {
+ key := parts[0]
+ childKeys := parts[1:]
+ // isEnd is the end of url path, means node is a place of url end,so the path with parentNode has a real url exists.
+ isEnd := len(childKeys) == 0
+ if isEnd {
+
+ if node.children != nil && node.children[key] != nil && node.children[key].endOfPath {
+ return node.children[key], []string{}, true
+ }
+ //consider trie node :/aaa/bbb/xxxxx/ccc/ddd /aaa/bbb/:id/ccc and request url is :/aaa/bbb/xxxxx/ccc
+ if node.PathVariableNode != nil {
+ if node.PathVariableNode.endOfPath {
+ return node.PathVariableNode, []string{key}, true
+ }
+ }
+
+ } else {
+ if node.children != nil && node.children[key] != nil {
+ n, param, ok := node.children[key].Match(childKeys)
+ if ok {
+ return n, param, ok
+ }
+ }
+ if node.PathVariableNode != nil {
+ n, param, ok := node.PathVariableNode.Match(childKeys)
+ param = append(param, key)
+ if ok {
+ return n, param, ok
+ }
+ }
+ }
+ if node.children != nil && node.children[key] != nil && node.children[key].MatchAllNode != nil {
+ return node.children[key].MatchAllNode, []string{}, true
+ }
+ if node.MatchAllNode != nil {
+ return node.MatchAllNode, []string{}, true
+ }
+ return nil, nil, false
+}
+
+//Get node get
+// returns:
+//*Node this node in path, if not exists return nil
+//[]string key reversed array of pathVariable /:aa/:bb/:cc returns array of (cc,bb,aa)
+//bool is ok
+//error
+func (node *Node) Get(keys []string) (*Node, []string, bool, error) {
+ key := keys[0]
+ childKeys := keys[1:]
+ isReal := len(childKeys) == 0
+ if isReal {
+ // exit condition
+ if stringutil.IsPathVariableOrWildcard(key) {
+ if node.PathVariableNode == nil || !node.PathVariableNode.endOfPath {
+ return nil, nil, false, nil
+ }
+ return node.PathVariableNode, []string{stringutil.VariableName(key)}, true, nil
+ } else if stringutil.IsMatchAll(key) {
+ return node.MatchAllNode, nil, true, nil
+ } else {
+ if node.children == nil {
+ return nil, nil, false, nil
+ }
+ return node.children[key], nil, true, nil
+ }
+ } else {
+
+ if stringutil.IsPathVariableOrWildcard(key) {
+ if node.PathVariableNode == nil {
+ return nil, nil, false, nil
+ }
+ retNode, pathVariableList, ok, e := node.PathVariableNode.Get(childKeys)
+ newList := []string{key}
+ copy(newList[1:], pathVariableList)
+ return retNode, newList, ok, e
+ } else if stringutil.IsMatchAll(key) {
+ return nil, nil, false, errors.Errorf("router configuration is empty")
+ } else {
+ if node.children == nil || node.children[key] == nil {
+ return nil, nil, false, nil
+ }
+ return node.children[key].Get(childKeys)
+ }
+ }
+
+}
+
+func (node *Node) put(key string, isReal bool, bizInfo interface{}) bool {
+
+ if !stringutil.IsPathVariableOrWildcard(key) {
+ if stringutil.IsMatchAll(key) {
+ return node.putMatchAllNode(key, isReal, bizInfo)
+ } else {
+ return node.putNode(key, isReal, bizInfo)
+ }
+ }
+ pathVariable := stringutil.VariableName(key)
+ return node.putPathVariable(pathVariable, isReal, bizInfo)
+}
+
+func (node *Node) putPathVariable(pathVariable string, isReal bool, bizInfo interface{}) bool {
+ //path variable put
+ if node.PathVariableNode == nil {
+ node.PathVariableNode = &Node{endOfPath: false}
+ }
+ if node.PathVariableNode.endOfPath && isReal {
+ //has a node with same path exists. conflicted.
+ return false
+ }
+ if isReal {
+ node.PathVariableNode.bizInfo = bizInfo
+ node.PathVariableNode.matchStr = pathVariable
+ }
+ node.PathVariableNode.endOfPath = node.PathVariableNode.endOfPath || isReal
+ if node.PathVariablesSet == nil {
+ node.PathVariablesSet = map[string]*Node{}
+ }
+ node.PathVariablesSet[pathVariable] = node.PathVariableNode
+ return true
+}
+
+func (node *Node) putNode(matchStr string, isReal bool, bizInfo interface{}) bool {
+
+ selfNode := &Node{endOfPath: isReal, matchStr: matchStr}
+ old := node.children[matchStr]
+ if old != nil {
+ if old.endOfPath && isReal {
+ // already has one same path url
+ return false
+ }
+ selfNode = old
+ } else {
+ old = selfNode
+ }
+
+ if isReal {
+ selfNode.bizInfo = bizInfo
+ }
+ selfNode.endOfPath = selfNode.endOfPath || old.endOfPath
+ node.children[matchStr] = selfNode
+ return true
+}
+
+func (node *Node) putMatchAllNode(matchStr string, isReal bool, bizInfo interface{}) bool {
+
+ selfNode := &Node{endOfPath: isReal, matchStr: matchStr}
+ old := node.MatchAllNode
+ if old != nil {
+ if old.endOfPath && isReal {
+ // already has one same path url
+ return false
+ }
+ selfNode = old
+ } else {
+ old = selfNode
+ }
+
+ if isReal {
+ selfNode.bizInfo = bizInfo
+ }
+ selfNode.endOfPath = selfNode.endOfPath || old.endOfPath
+ node.MatchAllNode = selfNode
+ return true
+}
diff --git a/pkg/common/router/trie/trie_test.go b/pkg/common/router/trie/trie_test.go
new file mode 100644
index 0000000..0585734
--- /dev/null
+++ b/pkg/common/router/trie/trie_test.go
@@ -0,0 +1,133 @@
+/*
+ * 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 trie
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestTrie_Put(t *testing.T) {
+ trie := NewTrie()
+ ret, _ := trie.Put("/path1/:pathvarible1/path2/:pathvarible2", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path1/:pathvarible1/path2/:pathvarible2/**", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/:pathvarible1/path2/:pathvarible2", "")
+ assert.True(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2", "")
+ assert.False(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2/3", "")
+ assert.True(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:432423/3", "")
+ assert.False(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/d/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/c/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/c/:fdsafdsafsdafsda", "")
+ assert.False(t, ret)
+
+ ret, _ = trie.Put("/path1/:pathvarible1/path2/:pathvarible2/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path1/:432/path2/:34", "")
+ assert.False(t, ret)
+}
+
+func TestTrie_MatchAndGet(t *testing.T) {
+ trie := NewTrie()
+
+ ret, _ := trie.Put("/path1/:pathvarible1/path2/:pathvarible2", "test1")
+ assert.True(t, ret)
+
+ _, _ = trie.Put("/a/b", "ab")
+ result, _, _ := trie.Match("/a/b")
+ assert.Equal(t, result.GetBizInfo(), "ab")
+
+ _, _ = trie.Put("POST/api/v1/**", "ab")
+ result, _, _ = trie.Match("POST/api/v1")
+ assert.Equal(t, "ab", result.GetBizInfo())
+
+ ret, _ = trie.Put("/path2/:pathvarible1/path2/:pathvarible2", "")
+ assert.True(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2", "")
+ assert.False(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:pathvarible2/3", "")
+ assert.True(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:432423/3", "")
+ assert.False(t, ret)
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/d/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/c/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path2/3/path2/:432423/3/a/b/c/c/:fdsafdsafsdafsda", "")
+ assert.False(t, ret)
+
+ ret, _ = trie.Put("/path1/:pathvarible1/path2/:pathvarible2/:fdsa", "")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/path1/:432/path2/:34", "")
+ assert.False(t, ret)
+
+ ret, _ = trie.Put("/a/:432/b/:34/**", "test**")
+ assert.True(t, ret)
+
+ ret, _ = trie.Put("/a/:432/b/:34/**", "")
+ assert.False(t, ret)
+
+ node, param, ok := trie.Match("/a/v1/b/v2/sadf/asdf")
+ assert.Equal(t, (param)[0], "v1")
+ assert.Equal(t, (param)[1], "v2")
+ assert.Equal(t, node.GetBizInfo(), "test**")
+ assert.True(t, ok)
+
+ _, param, ok = trie.Match("/a/v1/b/v2/sadf")
+ assert.Equal(t, (param)[0], "v1")
+ assert.Equal(t, (param)[1], "v2")
+ assert.Equal(t, node.GetBizInfo(), "test**")
+ assert.True(t, ok)
+
+ node, param, ok = trie.Match("/path1/v1/path2/v2")
+ assert.True(t, ok)
+ assert.True(t, len(param) == 2)
+ assert.Equal(t, (param)[0], "v1")
+ assert.Equal(t, (param)[1], "v2")
+ assert.True(t, node.GetBizInfo() == "test1")
+
+ _, _, ok, _ = trie.Get("/path1/v1/path2/v2")
+ assert.False(t, ok)
+
+ node, _, ok, _ = trie.Get("/path1/:p/path2/:p2")
+ assert.True(t, ok)
+ assert.True(t, node.GetBizInfo() == "test1")
+}
diff --git a/pkg/common/util/stringutil/stringutil.go b/pkg/common/util/stringutil/stringutil.go
index 8396e67..3e0f635 100644
--- a/pkg/common/util/stringutil/stringutil.go
+++ b/pkg/common/util/stringutil/stringutil.go
@@ -17,6 +17,11 @@
package stringutil
+import (
+ "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+ "strings"
+)
+
// StrInSlice returns whether the string is in the slice.
func StrInSlice(str string, slice []string) bool {
for _, s := range slice {
@@ -26,3 +31,52 @@ func StrInSlice(str string, slice []string) bool {
}
return false
}
+
+//Split url split to []string by "/"
+func Split(path string) []string {
+ return strings.Split(strings.TrimLeft(path, constant.PathSlash), constant.PathSlash)
+}
+
+//VariableName extract VariableName (:id, name = id)
+func VariableName(key string) string {
+ return strings.TrimPrefix(key, constant.PathParamIdentifier)
+}
+
+//IsPathVariableOrWildcard return if is a PathVariable (:id, true)
+func IsPathVariableOrWildcard(key string) bool {
+ if key == "" {
+ return false
+ }
+ if strings.HasPrefix(key, constant.PathParamIdentifier) {
+ return true
+ }
+
+ if IsWildcard(key) {
+ return true
+ }
+ //return key[0] == '{' && key[len(key)-1] == '}'
+ return false
+}
+
+//IsWildcard return if is *
+func IsWildcard(key string) bool {
+ return key == "*"
+}
+
+func IsMatchAll(key string) bool {
+ return key == "**"
+}
+
+func GetTrieKey(method string, path string) string {
+ ret := ""
+ if strings.HasPrefix(path, constant.PathSlash) {
+ ret = method + path
+ } else {
+ ret = method + constant.PathSlash + path
+ }
+
+ if strings.HasSuffix(ret, constant.PathSlash) {
+ ret = ret[0 : len(ret)-1]
+ }
+ return ret
+}
diff --git a/pkg/config/conf_test.yaml b/pkg/config/conf_test.yaml
index 93b14f4..2d4e6fb 100644
--- a/pkg/config/conf_test.yaml
+++ b/pkg/config/conf_test.yaml
@@ -41,14 +41,8 @@ static_resources:
match:
prefix: "/api/v1"
path: ""
- regex: ""
methods:
- POST
- headers:
- - name: "X-DGP-WAY"
- regex: false
- values:
- - "Dubbo"
route:
cluster: "test_dubbo"
cluster_not_found_response_code: 505
diff --git a/pkg/config/config_load.go b/pkg/config/config_load.go
index 429d0fe..16d8b5b 100644
--- a/pkg/config/config_load.go
+++ b/pkg/config/config_load.go
@@ -137,6 +137,7 @@ func GetHttpConfig(cfg *model.Bootstrap) (err error) {
logger.Error(err)
}
cfg.StaticResources.Listeners[i].Config = *hc
+ //cfg.StaticResources.Listeners[i].FilterChains
}
}
}
diff --git a/pkg/config/config_load_test.go b/pkg/config/config_load_test.go
index 22704db..94aef4d 100644
--- a/pkg/config/config_load_test.go
+++ b/pkg/config/config_load_test.go
@@ -48,14 +48,7 @@ func TestMain(m *testing.M) {
Methods: []string{
"POST",
},
- Path: "",
- Regex: "",
- Headers: []model.HeaderMatcher{
- {Name: "X-DGP-WAY",
- Values: []string{"Dubbo"},
- Regex: false,
- },
- },
+ Path: "",
},
Route: model.RouteAction{
Cluster: "test_dubbo",
diff --git a/pkg/context/http/util.go b/pkg/context/http/util.go
deleted file mode 100644
index b073ab7..0000000
--- a/pkg/context/http/util.go
+++ /dev/null
@@ -1,62 +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 http
-
-import (
- "regexp"
- "strings"
-)
-
-import (
- "github.com/apache/dubbo-go-pixiu/pkg/config"
- "github.com/apache/dubbo-go-pixiu/pkg/model"
-)
-
-// HttpRouteMatch
-func HttpRouteMatch(c *HttpContext, rm model.RouterMatch) bool {
- if rm.Prefix != "" {
- if !strings.HasPrefix(c.GetUrl(), rm.Path) {
- return false
- }
- }
-
- if rm.Path != "" {
- if c.GetUrl() != rm.Path {
- return false
- }
- }
-
- if rm.Regex != "" {
- if !regexp.MustCompile(rm.Regex).MatchString(c.GetUrl()) {
- return false
- }
- }
-
- return true
-}
-
-// HttpRouteActionMatch
-func HttpRouteActionMatch(c *HttpContext, ra model.RouteAction) bool {
- conf := config.GetBootstrap()
-
- if ra.Cluster == "" || !conf.ExistCluster(ra.Cluster) {
- return false
- }
-
- return true
-}
diff --git a/pkg/model/router.go b/pkg/model/router.go
index cfaa569..5314418 100644
--- a/pkg/model/router.go
+++ b/pkg/model/router.go
@@ -20,7 +20,6 @@ package model
import (
stdHttp "net/http"
"regexp"
- "strings"
)
import (
@@ -28,6 +27,7 @@ import (
)
import (
+ "github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
"github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
)
@@ -41,12 +41,12 @@ type (
// RouterMatch
RouterMatch struct {
- Prefix string `yaml:"prefix" json:"prefix" mapstructure:"prefix"`
- Path string `yaml:"path" json:"path" mapstructure:"path"`
- Regex string `yaml:"regex" json:"regex" mapstructure:"regex"`
- Methods []string `yaml:"methods" json:"methods" mapstructure:"methods"`
- Headers []HeaderMatcher `yaml:"headers" json:"headers" mapstructure:"headers"`
- pathRE *regexp.Regexp
+ Prefix string `yaml:"prefix" json:"prefix" mapstructure:"prefix"`
+ Path string `yaml:"path" json:"path" mapstructure:"path"`
+ // Regex string `yaml:"regex" json:"regex" mapstructure:"regex"` TODO: next version
+ Methods []string `yaml:"methods" json:"methods" mapstructure:"methods"`
+ // Headers []HeaderMatcher `yaml:"headers" json:"headers" mapstructure:"headers"`
+ // pathRE *regexp.Regexp
}
// RouteAction match route should do
@@ -57,8 +57,9 @@ type (
// RouteConfiguration
RouteConfiguration struct {
- Routes []*Router `yaml:"routes" json:"routes" mapstructure:"routes"`
- Dynamic bool `yaml:"dynamic" json:"dynamic" mapstructure:"dynamic"`
+ RouteTrie trie.Trie `yaml:"-" json:"-" mapstructure:"-"`
+ Routes []*Router `yaml:"routes" json:"routes" mapstructure:"routes"`
+ Dynamic bool `yaml:"dynamic" json:"dynamic" mapstructure:"dynamic"`
}
// Name header key, Value header value, Regex header value is regex
@@ -71,83 +72,17 @@ type (
)
func (rc *RouteConfiguration) Route(req *stdHttp.Request) (*RouteAction, error) {
- if rc.Routes == nil {
+ if rc.RouteTrie.IsEmpty() {
return nil, errors.Errorf("router configuration is empty")
}
- for _, r := range rc.Routes {
- if r.MatchRouter(req) {
- return &r.Route, nil
- }
+ node, _, _ := rc.RouteTrie.Match(stringutil.GetTrieKey(req.Method, req.URL.Path))
+ if node == nil {
+ return nil, errors.Errorf("route failed for %s,no rules matched.", stringutil.GetTrieKey(req.Method, req.URL.Path))
}
-
- return nil, errors.Errorf("no matched route")
-}
-
-// MatchRouter find router (cluster) by request path and method and header
-func (r *Router) MatchRouter(req *stdHttp.Request) bool {
- if !r.Match.matchPath(req) {
- return false
- }
-
- if !r.Match.matchMethod(req) {
- return false
- }
-
- if !r.Match.matchHeader(req) {
- return false
- }
-
- return true
-}
-
-func (rm *RouterMatch) matchPath(req *stdHttp.Request) bool {
- if rm.Path == "" && rm.Prefix == "" && rm.Regex == "" {
- return true
- }
-
- path := req.URL.Path
-
- if rm.Path != "" && rm.Path == path {
- return true
+ if node.GetBizInfo() == nil {
+ return nil, errors.Errorf("info is nil.please check your configuration.")
}
- if rm.Prefix != "" && strings.HasPrefix(path, rm.Prefix) {
- return true
- }
- if rm.Regex != "" {
- if rm.pathRE == nil {
- rm.pathRE = regexp.MustCompile(rm.Regex)
- }
- return rm.pathRE.MatchString(path)
- }
-
- return false
-}
-
-func (rm *RouterMatch) matchMethod(req *stdHttp.Request) bool {
- if len(rm.Methods) == 0 {
- return true
- }
-
- return stringutil.StrInSlice(req.Method, rm.Methods)
-}
-
-func (rm *RouterMatch) matchHeader(req *stdHttp.Request) bool {
- if len(rm.Headers) == 0 {
- return true
- }
-
- for _, h := range rm.Headers {
- // notice use canonical keys
- v := req.Header.Get(h.Name)
- if stringutil.StrInSlice(v, h.Values) {
- return true
- }
-
- if h.valueRE != nil && h.valueRE.MatchString(v) {
- return true
- }
- }
-
- return false
+ ret := (node.GetBizInfo()).(RouteAction)
+ return &ret, nil
}
diff --git a/pkg/model/router_test.go b/pkg/model/router_test.go
deleted file mode 100644
index 511b03a..0000000
--- a/pkg/model/router_test.go
+++ /dev/null
@@ -1,267 +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 model
-
-import (
- "net/http"
- "net/url"
- "testing"
-)
-
-import (
- "github.com/stretchr/testify/assert"
-)
-
-func TestRouterPathMatch(t *testing.T) {
- rm := &RouterMatch{
- Prefix: "/user/",
- Path: "/user2/test",
- Regex: "/user3/test",
- Methods: []string{"POST"},
- Headers: []HeaderMatcher{
- {
- Name: "content-type",
- Values: []string{
- "grpc",
- "json",
- },
- },
- },
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user/add",
- },
- }
- assert.True(t, rm.matchPath(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user2/add",
- },
- }
- assert.False(t, rm.matchPath(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user2/test",
- },
- }
- assert.True(t, rm.matchPath(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- }
- assert.True(t, rm.matchPath(req))
- }
-}
-
-func TestRouterMethodMatch(t *testing.T) {
- rm := &RouterMatch{
- Prefix: "/user",
- Methods: []string{"POST", "PUT"},
- Headers: []HeaderMatcher{
- {
- Name: "content-type",
- Values: []string{
- "grpc",
- "json",
- },
- },
- },
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- }
- assert.True(t, rm.matchMethod(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "PUT",
- }
- assert.True(t, rm.matchMethod(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "GET",
- }
- assert.False(t, rm.matchMethod(req))
- }
-}
-
-func TestRouterHeaderMatch(t *testing.T) {
- rm := &RouterMatch{
- Prefix: "/user",
- Path: "/user2/test",
- Regex: "/user3/test",
- Methods: []string{"POST", "PUT"},
- Headers: []HeaderMatcher{
- {
- Name: "content-type",
- Values: []string{
- "grpc",
- },
- },
- },
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc",
- },
- },
- }
- assert.True(t, rm.matchHeader(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- }
- assert.False(t, rm.matchHeader(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc2",
- },
- },
- }
- assert.False(t, rm.matchHeader(req))
- }
-}
-
-func TestRouterMatch(t *testing.T) {
- rm := RouterMatch{
- Prefix: "/user/",
- Path: "/user2/test",
- Regex: "/user3/test",
- Methods: []string{"POST"},
- Headers: []HeaderMatcher{
- {
- Name: "content-type",
- Values: []string{
- "grpc",
- "json",
- },
- },
- },
- }
-
- router := &Router{
- Match: rm,
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc",
- },
- },
- }
- assert.True(t, router.MatchRouter(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/us3/test",
- },
- Method: "POST",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc",
- },
- },
- }
- assert.False(t, router.MatchRouter(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST11",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc",
- },
- },
- }
- assert.False(t, router.MatchRouter(req))
- }
-
- {
- req := &http.Request{
- URL: &url.URL{
- Path: "/user3/test",
- },
- Method: "POST",
- Header: map[string][]string{
- "Content-Type": []string{
- "grpc1111",
- },
- },
- }
- assert.False(t, router.MatchRouter(req))
- }
-}
diff --git a/pkg/server/http.go b/pkg/server/http.go
index 1e5e67b..b68f6d0 100644
--- a/pkg/server/http.go
+++ b/pkg/server/http.go
@@ -19,6 +19,7 @@ package server
import (
"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+ "github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
"github.com/apache/dubbo-go-pixiu/pkg/model"
)
@@ -26,16 +27,9 @@ import (
func DefaultHttpConnectionManager() *model.HttpConnectionManagerConfig {
return &model.HttpConnectionManagerConfig{
RouteConfig: model.RouteConfiguration{
- Routes: []*model.Router{
- {
- Match: model.RouterMatch{
- Prefix: "/api/v1",
- },
- Route: model.RouteAction{
- Cluster: constant.HeaderValueAll,
- },
- },
- },
+ RouteTrie: trie.NewTrieWithDefault("/api/v1/**", model.RouteAction{
+ Cluster: constant.HeaderValueAll,
+ }),
},
HTTPFilters: []*model.HTTPFilter{
{
diff --git a/samples/http/simple/server/app/server.go b/samples/http/simple/server/app/server.go
index 1597f80..b923684 100644
--- a/samples/http/simple/server/app/server.go
+++ b/samples/http/simple/server/app/server.go
@@ -38,7 +38,7 @@ func main() {
func user(w http.ResponseWriter, r *http.Request) {
switch r.Method {
- case "POST":
+ case constant.Post:
byts, err := ioutil.ReadAll(r.Body)
if err != nil {
w.Write([]byte(err.Error()))
@@ -62,7 +62,7 @@ func user(w http.ResponseWriter, r *http.Request) {
return
}
w.Write(nil)
- case "GET":
+ case constant.Get:
subPath := strings.TrimPrefix(r.URL.Path, "/user/")
userName := strings.Split(subPath, "/")[0]
var u *User