blob: d8cbc37d57dfeae19ee617167f47f57b6b318fd9 [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 (
stdHttp "net/http"
"strings"
"sync"
)
import (
"github.com/pkg/errors"
)
import (
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/common/constant"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/common/router/trie"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/common/util/stringutil"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/context/http"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/logger"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/model"
"github.com/apache/dubbo-go-pixiu/pixiu/pkg/server"
)
type (
// RouterCoordinator the router coordinator for http connection manager
RouterCoordinator struct {
activeConfig *model.RouteConfiguration
rw sync.RWMutex
}
)
// CreateRouterCoordinator create coordinator for http connection manager
func CreateRouterCoordinator(routeConfig *model.RouteConfiguration) *RouterCoordinator {
rc := &RouterCoordinator{activeConfig: routeConfig}
if routeConfig.Dynamic {
server.GetRouterManager().AddRouterListener(rc)
}
rc.initTrie()
rc.initRegex()
return rc
}
// Route find routeAction for request
func (rm *RouterCoordinator) Route(hc *http.HttpContext) (*model.RouteAction, error) {
rm.rw.RLock()
defer rm.rw.RUnlock()
return rm.route(hc.Request)
}
func (rm *RouterCoordinator) RouteByPathAndName(path, method string) (*model.RouteAction, error) {
rm.rw.RLock()
defer rm.rw.RUnlock()
return rm.activeConfig.RouteByPathAndMethod(path, method)
}
func (rm *RouterCoordinator) route(req *stdHttp.Request) (*model.RouteAction, error) {
// match those route that only contains headers first
var matched []*model.Router
for _, route := range rm.activeConfig.Routes {
if len(route.Match.Prefix) > 0 {
continue
}
if route.Match.MatchHeader(req) {
matched = append(matched, route)
}
}
// always return the first match of header if got any
if len(matched) > 0 {
if len(matched[0].Route.Cluster) == 0 {
return nil, errors.New("action is nil. please check your configuration.")
}
return &matched[0].Route, nil
}
// match those route that only contains prefix
// TODO: may consider implementing both prefix and header in the future
return rm.activeConfig.Route(req)
}
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)
}
}
func (rm *RouterCoordinator) initRegex() {
for _, router := range rm.activeConfig.Routes {
headers := router.Match.Headers
for i := range headers {
if headers[i].Regex && len(headers[i].Values) > 0 {
// regexp always use first value of header
err := headers[i].SetValueRegex(headers[i].Values[0])
if err != nil {
logger.Errorf("invalid regexp in headers[%d]: %v", i, err)
panic(err)
}
}
}
}
}
// OnAddRouter add router
func (rm *RouterCoordinator) OnAddRouter(r *model.Router) {
//TODO: lock move to trie node
rm.rw.Lock()
defer rm.rw.Unlock()
if r.Match.Methods == nil {
r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post, constant.Options}
}
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)
}
}
// OnDeleteRouter delete router
func (rm *RouterCoordinator) OnDeleteRouter(r *model.Router) {
rm.rw.Lock()
defer rm.rw.Unlock()
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)
}
}