gf/g/net/ghttp/ghttp_server_router.go

329 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// 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://gitee.com/johng/gf.
// 路由控制基本方法.
package ghttp
import (
"container/list"
"errors"
"fmt"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/util/gstr"
"runtime"
"strings"
)
// 解析pattern
func (s *Server)parsePattern(pattern string) (domain, method, uri string, err error) {
uri = pattern
domain = gDEFAULT_DOMAIN
method = gDEFAULT_METHOD
if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
method = array[1]
uri = array[2]
}
if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, uri); len(array) > 1 && err == nil {
uri = array[1]
domain = array[2]
}
if uri == "" {
err = errors.New("invalid pattern")
}
// 去掉末尾的"/"符号,与路由匹配时处理一致
if uri != "/" {
uri = strings.TrimRight(uri, "/")
}
return
}
// 获得服务注册的文件地址信息
func (s *Server) getHandlerRegisterCallerLine(handler *handlerItem) string {
skip := 5
if handler.rtype == gROUTE_REGISTER_HANDLER {
skip = 4
}
if _, cfile, cline, ok := runtime.Caller(skip); ok {
return fmt.Sprintf("%s:%d", cfile, cline)
}
return ""
}
// 路由注册处理方法。
// 如果带有hook参数表示是回调注册方法; 否则为普通路由执行方法。
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) (resultErr error) {
// Web Server正常运行时无法动态注册路由方法
if s.Status() == SERVER_STATUS_RUNNING {
return errors.New("cannot bind handler while server running")
}
var hookName string
if len(hook) > 0 {
hookName = hook[0]
}
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
return errors.New("invalid pattern")
}
// 注册地址记录及重复注册判断
regkey := s.handlerKey(hookName, method, uri, domain)
caller := s.getHandlerRegisterCallerLine(handler)
if len(hook) == 0 {
if item, ok := s.routesMap[regkey]; ok {
s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
glog.Errorfln(s)
return errors.New(s)
}
}
defer func() {
if resultErr == nil {
if _, ok := s.routesMap[regkey]; !ok {
s.routesMap[regkey] = make([]registeredRouteItem, 0)
}
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
file : caller,
handler : handler,
})
}
}()
// 路由对象
handler.router = &Router {
Uri : uri,
Domain : domain,
Method : method,
Priority : strings.Count(uri[1:], "/"),
}
handler.router.RegRule, handler.router.RegNames = s.patternToRegRule(uri)
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
// 非叶节点为哈希表检索节点按照URI注册的层级进行高效检索直至到叶子链表节点
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
tree := (map[string]interface{})(nil)
if len(hookName) == 0 {
tree = s.serveTree
} else {
tree = s.hooksTree
}
if _, ok := tree[domain]; !ok {
tree[domain] = make(map[string]interface{})
}
// 用于遍历的指针
p := tree[domain]
if len(hookName) > 0 {
if _, ok := p.(map[string]interface{})[hookName]; !ok {
p.(map[string]interface{})[hookName] = make(map[string]interface{})
}
p = p.(map[string]interface{})[hookName]
}
// 当前节点的规则链表
lists := make([]*list.List, 0)
array := ([]string)(nil)
if strings.EqualFold("/", uri) {
array = []string{"/"}
} else {
array = strings.Split(uri[1:], "/")
}
// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
for k, v := range array {
if len(v) == 0 {
continue
}
// 判断是否模糊匹配规则
if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, v) {
v = "*fuzz"
// 由于是模糊规则,因此这里会有一个*list用以将后续的路由规则加进来
// 检索会从叶子节点的链表往根节点按照优先级进行检索
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
}
// 属性层级数据写入
if _, ok := p.(map[string]interface{})[v]; !ok {
p.(map[string]interface{})[v] = make(map[string]interface{})
}
p = p.(map[string]interface{})[v]
// 到达叶子节点往list中增加匹配规则(条件 v != "*fuzz" 是因为模糊节点的话在前面已经添加了*list链表)
if k == len(array) - 1 && v != "*fuzz" {
if v, ok := p.(map[string]interface{})["*list"]; !ok {
p.(map[string]interface{})["*list"] = list.New()
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
} else {
lists = append(lists, v.(*list.List))
}
}
}
// 上面循环后得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)。
// 下面从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在lists链表的前面)
item := (*handlerItem)(nil)
for _, l := range lists {
pushed := false
for e := l.Front(); e != nil; e = e.Next() {
item = e.Value.(*handlerItem)
// 判断是否已存在相同的路由注册项,(如果不是hook注册)是则进行替换
if len(hookName) == 0 {
if strings.EqualFold(handler.router.Domain, item.router.Domain) &&
strings.EqualFold(handler.router.Method, item.router.Method) &&
strings.EqualFold(handler.router.Uri, item.router.Uri) {
e.Value = handler
pushed = true
break
}
}
// 如果路由注册项不相等,那么判断优先级,决定插入顺序
if s.compareRouterPriority(handler.router, item.router) {
l.InsertBefore(handler, e)
pushed = true
break
}
}
if !pushed {
l.PushBack(handler)
}
}
//gutil.Dump(s.serveTree)
//gutil.Dump(s.hooksTree)
return nil
}
// 对比两个handlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序。
// 返回值true表示newRouter优先级比oldRouter高会被添加链表中oldRouter的前面否则后面。
// 优先级比较规则:
// 1、层级越深优先级越高(对比/数量)
// 2、模糊规则优先级{xxx} > :xxx > *xxx
func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
// 优先比较层级,层级越深优先级越高
if newRouter.Priority > oldRouter.Priority {
return true
}
if newRouter.Priority < oldRouter.Priority {
return false
}
// 精准匹配比模糊匹配规则优先级高,例如:/name/act 比 /{name}/:act 优先级高
var fuzzyCountFieldNew, fuzzyCountFieldOld int
var fuzzyCountNameNew, fuzzyCountNameOld int
var fuzzyCountAnyNew, fuzzyCountAnyOld int
var fuzzyCountTotalNew, fuzzyCountTotalOld int
for _, v := range newRouter.Uri {
switch v {
case '{':
fuzzyCountFieldNew++
case ':':
fuzzyCountNameNew++
case '*':
fuzzyCountAnyNew++
}
}
for _, v := range oldRouter.Uri {
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
}
/** 如果模糊规则数量相等,那么执行分别的数量判断 **/
// 例如:/name/{act} 比 /name/:act 优先级高
if fuzzyCountFieldNew > fuzzyCountFieldOld {
return true
}
if fuzzyCountFieldNew < fuzzyCountFieldOld {
return false
}
// 例如: /name/:act 比 /name/*act 优先级高
if fuzzyCountNameNew > fuzzyCountNameOld {
return true
}
if fuzzyCountNameNew < fuzzyCountNameOld {
return false
}
/* 模糊规则数量相等,后续不用再判断*规则的数量比较了 */
// 比较HTTP METHOD更精准的优先级更高
if newRouter.Method != gDEFAULT_METHOD {
return true
}
if oldRouter.Method != gDEFAULT_METHOD {
return true
}
// 最后新的规则比旧的规则优先级低
return false
}
// 将pattern不带method和domain解析成正则表达式匹配以及对应的query字符串
func (s *Server) patternToRegRule(rule string) (regrule string, names []string) {
if len(rule) < 2 {
return rule, nil
}
regrule = "^"
array := strings.Split(rule[1:], "/")
for _, v := range array {
if len(v) == 0 {
continue
}
switch v[0] {
case ':':
if len(v) > 1 {
regrule += `/([^/]+)`
names = append(names, v[1:])
break
} else {
regrule += `/[^/]+`
break
}
fallthrough
case '*':
if len(v) > 1 {
regrule += `/{0,1}(.*)`
names = append(names, v[1:])
break
} else {
regrule += `/{0,1}.*`
break
}
fallthrough
default:
// 特殊字符替换
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) {
regrule += "/" + v
} else {
regrule += "/" + s
}
}
}
regrule += `$`
return
}