blob: 806cda0b58d1115b60c9d873fe27fa815f07030f [file] [log] [blame]
/*
* 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(),
}
}