| /* |
| * 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 ( |
| "net/url" |
| "strings" |
| "sync" |
| ) |
| |
| import ( |
| "github.com/dubbogo/dubbo-go-pixiu-filter/pkg/api/config" |
| "github.com/dubbogo/dubbo-go-pixiu-filter/pkg/router" |
| "github.com/emirpasic/gods/trees/avltree" |
| "github.com/pkg/errors" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/common/constant" |
| ) |
| |
| // Node defines the single method of the router configured API |
| type Node struct { |
| fullPath string |
| wildcard bool |
| filters []string |
| methods map[config.HTTPVerb]*config.Method |
| headers map[string]string |
| } |
| |
| // Route defines the tree of router APIs |
| type Route struct { |
| lock sync.RWMutex |
| tree *avltree.Tree |
| wildcardTree *avltree.Tree |
| } |
| |
| // ClearAPI clear the api |
| func (rt *Route) ClearAPI() error { |
| rt.lock.Lock() |
| defer rt.lock.Unlock() |
| rt.wildcardTree.Clear() |
| rt.tree.Clear() |
| return nil |
| } |
| |
| // PutAPI puts an api into the resource |
| func (rt *Route) PutAPI(api router.API) error { |
| fullPath := api.URLPattern |
| node, ok := rt.findNode(fullPath) |
| rt.lock.Lock() |
| defer rt.lock.Unlock() |
| if !ok { |
| wildcard := strings.Contains(fullPath, constant.PathParamIdentifier) |
| rn := &Node{ |
| fullPath: fullPath, |
| methods: map[config.HTTPVerb]*config.Method{api.Method.HTTPVerb: &api.Method}, |
| wildcard: wildcard, |
| headers: api.Headers, |
| } |
| if wildcard { |
| rt.wildcardTree.Put(fullPath, rn) |
| } |
| rt.tree.Put(fullPath, rn) |
| return nil |
| } |
| return node.putMethod(api.Method, api.Headers) |
| } |
| |
| func (node *Node) putMethod(method config.Method, headers map[string]string) error { |
| if _, ok := node.methods[method.HTTPVerb]; ok { |
| return errors.Errorf("Method %s already exists in path %s", method.HTTPVerb, node.fullPath) |
| } |
| node.methods[method.HTTPVerb] = &method |
| node.headers = headers |
| return nil |
| } |
| |
| // UpdateAPI update the api method in the existing router node |
| func (rt *Route) UpdateAPI(api router.API) error { |
| node, found := rt.findNode(api.URLPattern) |
| if found { |
| if _, ok := node.methods[api.Method.HTTPVerb]; ok { |
| rt.lock.Lock() |
| defer rt.lock.Unlock() |
| node.methods[api.Method.HTTPVerb] = &api.Method |
| } |
| } |
| return nil |
| } |
| |
| // FindAPI returns the api that meets the |
| func (rt *Route) FindAPI(fullPath string, httpverb config.HTTPVerb) (*router.API, bool) { |
| if n, found := rt.findNode(fullPath); found { |
| rt.lock.RLock() |
| defer rt.lock.RUnlock() |
| if method, ok := n.methods[httpverb]; ok { |
| return &router.API{ |
| URLPattern: n.fullPath, |
| Method: *method, |
| Headers: n.headers, |
| }, ok |
| } |
| } |
| return nil, false |
| } |
| |
| // DeleteNode delete node by fullPath |
| func (rt *Route) DeleteNode(fullPath string) bool { |
| rt.lock.RLock() |
| defer rt.lock.RUnlock() |
| rt.tree.Remove(fullPath) |
| return true |
| } |
| |
| // DeleteAPI delete api by fullPath and http verb |
| func (rt *Route) DeleteAPI(fullPath string, httpverb config.HTTPVerb) bool { |
| if n, found := rt.findNode(fullPath); found { |
| rt.lock.RLock() |
| defer rt.lock.RUnlock() |
| delete(n.methods, httpverb) |
| return true |
| } |
| return false |
| } |
| |
| func (rt *Route) findNode(fullPath string) (*Node, bool) { |
| var n interface{} |
| var found bool |
| if n, found = rt.searchWildcard(fullPath); !found { |
| rt.lock.RLock() |
| defer rt.lock.RUnlock() |
| if n, found = rt.tree.Get(fullPath); !found { |
| return nil, false |
| } |
| } |
| return n.(*Node), found |
| } |
| |
| 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) != nil { |
| n, ok := rt.wildcardTree.Get(p) |
| return n.(*Node), ok |
| } |
| } |
| return nil, 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) url.Values { |
| cPaths := strings.Split(strings.TrimLeft(checkPath, constant.PathSlash), constant.PathSlash) |
| wPaths := strings.Split(strings.TrimLeft(wildcardPath, constant.PathSlash), constant.PathSlash) |
| result := url.Values{} |
| if len(cPaths) == 0 || len(wPaths) == 0 || len(cPaths) != len(wPaths) { |
| return nil |
| } |
| for i := 0; i < len(cPaths); i++ { |
| if !strings.EqualFold(cPaths[i], wPaths[i]) && !strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) { |
| return nil |
| } |
| if strings.HasPrefix(wPaths[i], constant.PathParamIdentifier) { |
| result.Add(strings.TrimPrefix(wPaths[i], constant.PathParamIdentifier), cPaths[i]) |
| } |
| } |
| return result |
| } |
| |
| // NewRoute returns an empty router tree |
| func NewRoute() *Route { |
| return &Route{ |
| tree: avltree.NewWithStringComparator(), |
| wildcardTree: avltree.NewWithStringComparator(), |
| } |
| } |