gf/net/ghttp/ghttp_server_router.go

418 lines
13 KiB
Go
Raw Normal View History

2021-01-17 21:46:25 +08:00
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2018-04-13 15:19:31 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
2018-04-13 15:19:31 +08:00
package ghttp
import (
2021-09-27 21:27:24 +08:00
"context"
2019-06-19 09:06:52 +08:00
"fmt"
2021-11-07 21:31:33 +08:00
"reflect"
2019-06-19 09:06:52 +08:00
"strings"
2019-07-29 21:01:19 +08:00
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/container/glist"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/debug/gdebug"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/protocol/goai"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/util/gmeta"
2018-04-13 15:19:31 +08:00
)
const (
2021-01-27 00:20:23 +08:00
stackFilterKey = "/net/ghttp/ghttp"
)
2020-03-04 22:52:56 +08:00
var (
// handlerIdGenerator is handler item id generator.
handlerIdGenerator = gtype.NewInt()
)
2020-03-05 16:08:55 +08:00
// routerMapKey creates and returns an unique router key for given parameters.
// This key is used for Server.routerMap attribute, which is mainly for checks for
// repeated router registering.
func (s *Server) routerMapKey(hook, method, path, domain string) string {
return hook + "%" + s.serveHandlerKey(method, path, domain)
}
// parsePattern parses the given pattern to domain, method and path variable.
2019-06-19 09:06:52 +08:00
func (s *Server) parsePattern(pattern string) (domain, method, path string, err error) {
path = strings.TrimSpace(pattern)
domain = DefaultDomainName
2020-12-14 13:26:48 +08:00
method = defaultMethod
2019-06-19 09:06:52 +08:00
if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
path = strings.TrimSpace(array[2])
if v := strings.TrimSpace(array[1]); v != "" {
method = v
}
}
if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, path); len(array) > 1 && err == nil {
path = strings.TrimSpace(array[1])
if v := strings.TrimSpace(array[2]); v != "" {
domain = v
}
}
if path == "" {
err = gerror.NewCode(gcode.CodeInvalidParameter, "invalid pattern: URI should not be empty")
2019-06-19 09:06:52 +08:00
}
if path != "/" {
path = strings.TrimRight(path, "/")
}
return
2018-04-13 15:19:31 +08:00
}
2021-11-07 21:31:33 +08:00
type setHandlerInput struct {
Prefix string
Pattern string
HandlerItem *handlerItem
}
// setHandler creates router item with given handler and pattern and registers the handler to the router tree.
// The router tree can be treated as a multilayer hash table, please refer to the comment in following codes.
// This function is called during server starts up, which cares little about the performance. What really cares
2021-09-27 21:27:24 +08:00
// is the well-designed router storage structure for router searching when the request is under serving.
2021-11-07 21:31:33 +08:00
func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
var (
prefix = in.Prefix
pattern = in.Pattern
handler = in.HandlerItem
)
handler.Id = handlerIdGenerator.Add(1)
if handler.Source == "" {
2021-01-27 00:20:23 +08:00
_, file, line := gdebug.CallerWithFilter(stackFilterKey)
handler.Source = fmt.Sprintf(`%s:%d`, file, line)
}
2019-06-19 09:06:52 +08:00
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
s.Logger().Fatalf(ctx, `invalid pattern "%s", %+v`, pattern, err)
2019-06-19 09:06:52 +08:00
return
}
2021-11-07 21:31:33 +08:00
// Change the registered route according meta info from its request structure.
if handler.Info.Type != nil && handler.Info.Type.NumIn() == 2 {
var (
objectReq = reflect.New(handler.Info.Type.In(1))
)
if v := gmeta.Get(objectReq, goai.TagNamePath); !v.IsEmpty() {
uri = v.String()
}
if v := gmeta.Get(objectReq, goai.TagNameMethod); !v.IsEmpty() {
method = v.String()
}
if v := gmeta.Get(objectReq, goai.TagNameDomain); !v.IsEmpty() {
domain = v.String()
}
}
// Prefix for URI feature.
if prefix != "" {
uri = prefix + "/" + strings.TrimLeft(uri, "/")
}
2019-06-19 09:06:52 +08:00
if len(uri) == 0 || uri[0] != '/' {
s.Logger().Fatalf(ctx, `invalid pattern "%s", URI should lead with '/'`, pattern)
2019-06-19 09:06:52 +08:00
return
}
// Repeated router checks, this feature can be disabled by server configuration.
routerKey := s.routerMapKey(handler.HookName, method, uri, domain)
if !s.config.RouteOverWrite {
switch handler.Type {
case HandlerTypeHandler, HandlerTypeObject:
2020-03-04 22:52:56 +08:00
if item, ok := s.routesMap[routerKey]; ok {
s.Logger().Fatalf(
2021-09-27 21:27:24 +08:00
ctx,
`duplicated route registry "%s" at %s , already registered at %s`,
pattern, handler.Source, item[0].Source,
)
return
}
2019-06-19 09:06:52 +08:00
}
}
// Create a new router by given parameter.
handler.Router = &Router{
2019-06-19 09:06:52 +08:00
Uri: uri,
Domain: domain,
Method: strings.ToUpper(method),
2019-06-19 09:06:52 +08:00
Priority: strings.Count(uri[1:], "/"),
}
handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri)
2018-07-27 19:03:32 +08:00
if _, ok := s.serveTree[domain]; !ok {
s.serveTree[domain] = make(map[string]interface{})
2019-06-19 09:06:52 +08:00
}
2020-03-05 16:08:55 +08:00
// List array, very important for router registering.
// There may be multiple lists adding into this array when searching from root to leaf.
lists := make([]*glist.List, 0)
2019-06-19 09:06:52 +08:00
array := ([]string)(nil)
if strings.EqualFold("/", uri) {
array = []string{"/"}
} else {
array = strings.Split(uri[1:], "/")
}
// Multilayer hash table:
// 1. Each node of the table is separated by URI path which is split by char '/'.
// 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name.
// 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM,
// especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item,
// and the leaf node also has "*list" item. If the node is not a fuzzy node either
// a leaf, it neither has "*list" item.
// 2. The "*list" item is a list containing registered router items ordered by their
// priorities from high to low.
// 3. There may be repeated router items in the router lists. The lists' priorities
// from root to leaf are from low to high.
p := s.serveTree[domain]
for i, part := range array {
// Ignore empty URI part, like: /user//index
if part == "" {
continue
}
// Check if it's a fuzzy node.
if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) {
part = "*fuzz"
// If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map.
// All the sub router items from this fuzzy node will also be added to its "*list" item.
if v, ok := p.(map[string]interface{})["*list"]; !ok {
newListForFuzzy := glist.New()
p.(map[string]interface{})["*list"] = newListForFuzzy
lists = append(lists, newListForFuzzy)
} else {
lists = append(lists, v.(*glist.List))
}
}
// Make a new bucket for current node.
if _, ok := p.(map[string]interface{})[part]; !ok {
p.(map[string]interface{})[part] = make(map[string]interface{})
}
// Loop to next bucket.
p = p.(map[string]interface{})[part]
// The leaf is a hash map and must have an item named "*list", which contains the router item.
// The leaf can be furthermore extended by adding more ket-value pairs into its map.
// Note that the `v != "*fuzz"` comparison is required as the list might be added in the former
// fuzzy checks.
if i == len(array)-1 && part != "*fuzz" {
if v, ok := p.(map[string]interface{})["*list"]; !ok {
2020-03-04 22:52:56 +08:00
leafList := glist.New()
p.(map[string]interface{})["*list"] = leafList
lists = append(lists, leafList)
2019-06-19 09:06:52 +08:00
} else {
lists = append(lists, v.(*glist.List))
2019-06-19 09:06:52 +08:00
}
}
}
// It iterates the list array of `lists`, compares priorities and inserts the new router item in
// the proper position of each list. The priority of the list is ordered from high to low.
2019-06-19 09:06:52 +08:00
item := (*handlerItem)(nil)
for _, l := range lists {
pushed := false
for e := l.Front(); e != nil; e = e.Next() {
item = e.Value.(*handlerItem)
// Checks the priority whether inserting the route item before current item,
2021-11-07 21:31:33 +08:00
// which means it has higher priority.
if s.compareRouterPriority(handler, item) {
l.InsertBefore(e, handler)
pushed = true
goto end
2019-06-19 09:06:52 +08:00
}
}
2019-08-06 23:10:37 +08:00
end:
// Just push back in default.
2019-06-19 09:06:52 +08:00
if !pushed {
l.PushBack(handler)
}
}
// Initialize the route map item.
2020-03-04 22:52:56 +08:00
if _, ok := s.routesMap[routerKey]; !ok {
s.routesMap[routerKey] = make([]registeredRouteItem, 0)
2019-06-19 09:06:52 +08:00
}
routeItem := registeredRouteItem{
Source: handler.Source,
Handler: handler,
}
switch handler.Type {
case HandlerTypeHandler, HandlerTypeObject:
// Overwrite the route.
2020-03-04 22:52:56 +08:00
s.routesMap[routerKey] = []registeredRouteItem{routeItem}
default:
// Append the route.
2020-03-04 22:52:56 +08:00
s.routesMap[routerKey] = append(s.routesMap[routerKey], routeItem)
}
2018-04-13 15:19:31 +08:00
}
// compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true
// if `newItem`'s priority is higher than `oldItem`, else it returns false. The higher priority
2021-11-07 21:31:33 +08:00
// item will be inserted into the router list before the other one.
2020-03-04 23:32:27 +08:00
//
// Comparison rules:
// 1. The middleware has the most high priority.
2021-11-07 21:31:33 +08:00
// 2. URI: The deeper, the higher (simply check the count of char '/' in the URI).
2020-03-04 23:32:27 +08:00
// 3. Route type: {xxx} > :xxx > *xxx.
func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerItem) bool {
2020-03-04 23:32:27 +08:00
// If they're all type of middleware, the priority is according their registered sequence.
if newItem.Type == HandlerTypeMiddleware && oldItem.Type == HandlerTypeMiddleware {
return false
}
2020-03-04 23:32:27 +08:00
// The middleware has the most high priority.
if newItem.Type == HandlerTypeMiddleware && oldItem.Type != HandlerTypeMiddleware {
return true
}
2021-11-07 21:31:33 +08:00
// URI: The deeper, the higher (simply check the count of char '/' in the URI).
if newItem.Router.Priority > oldItem.Router.Priority {
2019-06-19 09:06:52 +08:00
return true
}
if newItem.Router.Priority < oldItem.Router.Priority {
2019-06-19 09:06:52 +08:00
return false
}
// Compare the length of their URI,
// but the fuzzy and named parts of the URI are not calculated to the result.
// Example:
// /admin-goods-{page} > /admin-{page}
// /{hash}.{type} > /{hash}
var uriNew, uriOld string
uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.Router.Uri)
uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.Router.Uri)
uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew)
uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld)
uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any".
uriOld, _ = gregex.ReplaceString(`\*[^/]*`, "", uriOld) // Replace "/*" and "/*any".
if len(uriNew) > len(uriOld) {
return true
}
if len(uriNew) < len(uriOld) {
return false
}
// Route type checks: {xxx} > :xxx > *xxx.
// Example:
// /name/act > /{name}/:act
var (
fuzzyCountFieldNew int
fuzzyCountFieldOld int
fuzzyCountNameNew int
fuzzyCountNameOld int
fuzzyCountAnyNew int
fuzzyCountAnyOld int
fuzzyCountTotalNew int
fuzzyCountTotalOld int
)
for _, v := range newItem.Router.Uri {
2019-06-19 09:06:52 +08:00
switch v {
case '{':
fuzzyCountFieldNew++
case ':':
fuzzyCountNameNew++
case '*':
fuzzyCountAnyNew++
}
}
for _, v := range oldItem.Router.Uri {
2019-06-19 09:06:52 +08:00
switch v {
case '{':
fuzzyCountFieldOld++
case ':':
fuzzyCountNameOld++
case '*':
fuzzyCountAnyOld++
}
}
fuzzyCountTotalNew = fuzzyCountFieldNew + fuzzyCountNameNew + fuzzyCountAnyNew
fuzzyCountTotalOld = fuzzyCountFieldOld + fuzzyCountNameOld + fuzzyCountAnyOld
if fuzzyCountTotalNew < fuzzyCountTotalOld {
return true
}
if fuzzyCountTotalNew > fuzzyCountTotalOld {
return false
}
2020-03-04 23:32:27 +08:00
// If the counts of their fuzzy rules equal.
2020-03-04 23:32:27 +08:00
// Eg: /name/{act} > /name/:act
2019-06-19 09:06:52 +08:00
if fuzzyCountFieldNew > fuzzyCountFieldOld {
return true
}
if fuzzyCountFieldNew < fuzzyCountFieldOld {
return false
}
2020-03-04 23:32:27 +08:00
// Eg: /name/:act > /name/*act
2019-06-19 09:06:52 +08:00
if fuzzyCountNameNew > fuzzyCountNameOld {
return true
}
if fuzzyCountNameNew < fuzzyCountNameOld {
return false
}
2020-03-04 23:32:27 +08:00
// It then compares the accuracy of their http method,
// the more accurate the more priority.
if newItem.Router.Method != defaultMethod {
2019-06-19 09:06:52 +08:00
return true
}
if oldItem.Router.Method != defaultMethod {
2019-06-19 09:06:52 +08:00
return true
}
2020-03-04 23:32:27 +08:00
// If they have different router type,
// the new router item has more priority than the other one.
if newItem.Type == HandlerTypeHandler || newItem.Type == HandlerTypeObject {
2019-12-19 15:38:34 +08:00
return true
}
2020-03-04 23:32:27 +08:00
// Other situations, like HOOK items,
// the old router item has more priority than the other one.
2019-12-19 15:38:34 +08:00
return false
2018-04-13 15:19:31 +08:00
}
2020-03-04 23:32:27 +08:00
// patternToRegular converts route rule to according regular expression.
func (s *Server) patternToRegular(rule string) (regular string, names []string) {
2019-06-19 09:06:52 +08:00
if len(rule) < 2 {
return rule, nil
}
2020-03-04 23:32:27 +08:00
regular = "^"
2019-06-19 09:06:52 +08:00
array := strings.Split(rule[1:], "/")
for _, v := range array {
if len(v) == 0 {
continue
}
switch v[0] {
case ':':
if len(v) > 1 {
2020-03-04 23:32:27 +08:00
regular += `/([^/]+)`
2019-06-19 09:06:52 +08:00
names = append(names, v[1:])
} else {
2020-03-04 23:32:27 +08:00
regular += `/[^/]+`
2019-06-19 09:06:52 +08:00
}
case '*':
if len(v) > 1 {
2020-03-04 23:32:27 +08:00
regular += `/{0,1}(.*)`
2019-06-19 09:06:52 +08:00
names = append(names, v[1:])
} else {
2020-03-04 23:32:27 +08:00
regular += `/{0,1}.*`
2019-06-19 09:06:52 +08:00
}
default:
// Special chars replacement.
2019-06-19 09:06:52 +08:00
v = gstr.ReplaceByMap(v, map[string]string{
`.`: `\.`,
`+`: `\+`,
`*`: `.*`,
})
s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
names = append(names, s[1:len(s)-1])
return `([^/]+)`
})
if strings.EqualFold(s, v) {
2020-03-04 23:32:27 +08:00
regular += "/" + v
2019-06-19 09:06:52 +08:00
} else {
2020-03-04 23:32:27 +08:00
regular += "/" + s
2019-06-19 09:06:52 +08:00
}
}
}
2020-03-04 23:32:27 +08:00
regular += `$`
2019-06-19 09:06:52 +08:00
return
2018-04-13 15:19:31 +08:00
}