Files
gf/g/net/ghttp/ghttp_server_router.go

318 lines
11 KiB
Go
Raw Normal View History

2018-04-13 15:19:31 +08:00
// 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.
// 路由控制基本方法.
2018-04-13 15:19:31 +08:00
package ghttp
import (
"errors"
"strings"
"container/list"
2018-07-11 17:06:47 +08:00
"gitee.com/johng/gf/g/util/gregex"
2018-08-16 18:17:47 +08:00
"gitee.com/johng/gf/g/util/gstr"
"gitee.com/johng/gf/g/os/glog"
"fmt"
"runtime"
2018-04-13 15:19:31 +08:00
)
2018-04-13 15:19:31 +08:00
// 解析pattern
func (s *Server)parsePattern(pattern string) (domain, method, uri string, err error) {
2018-04-13 15:19:31 +08:00
uri = pattern
domain = gDEFAULT_DOMAIN
method = gDEFAULT_METHOD
2018-07-11 17:06:47 +08:00
if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
method = array[1]
uri = array[2]
2018-04-13 15:19:31 +08:00
}
2018-07-11 17:06:47 +08:00
if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, uri); len(array) > 1 && err == nil {
2018-04-13 15:19:31 +08:00
uri = array[1]
domain = array[2]
}
if uri == "" {
err = errors.New("invalid pattern")
}
// 去掉末尾的"/"符号,与路由匹配时处理一致
2018-04-16 16:23:34 +08:00
if uri != "/" {
uri = strings.TrimRight(uri, "/")
}
2018-04-13 15:19:31 +08:00
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 ""
}
2018-07-27 19:03:32 +08:00
// 路由注册处理方法。
// 如果带有hook参数表示是回调注册方法否则为普通路由执行方法。
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) (resultErr error) {
2018-07-29 22:01:29 +08:00
// Web Server正字运行时无法动态注册路由方法
2018-08-01 13:04:15 +08:00
if s.Status() == SERVER_STATUS_RUNNING {
2018-08-02 15:23:14 +08:00
return errors.New("cannot bind handler while server running")
2018-07-29 22:01:29 +08:00
}
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.hookHandlerKey(hookName, method, uri, domain)
caller := s.getHandlerRegisterCallerLine(handler)
if line, ok := s.routesMap[regkey]; ok {
s := fmt.Sprintf(`duplicated route registry "%s" in %s , former in %s`, pattern, caller, line)
glog.Errorfln(s)
return errors.New(s)
} else {
defer func() {
if resultErr == nil {
s.routesMap[regkey] = registeredRouteItem{
file : caller,
handler : handler,
}
}
}()
}
2018-07-27 19:03:32 +08:00
// 路由对象
2018-08-02 15:23:14 +08:00
handler.router = &Router {
2018-07-27 19:03:32 +08:00
Uri : uri,
Domain : domain,
Method : method,
Priority : strings.Count(uri[1:], "/"),
}
2018-08-02 15:23:14 +08:00
handler.router.RegRule, handler.router.RegNames = s.patternToRegRule(uri)
2018-07-27 19:03:32 +08:00
2018-07-29 22:01:29 +08:00
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
// 非叶节点为哈希表检索节点按照URI注册的层级进行高效检索直至到叶子链表节点
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
2018-08-02 15:23:14 +08:00
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{})
2018-07-29 22:01:29 +08:00
}
// 用于遍历的指针
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]
}
2018-07-29 22:01:29 +08:00
// 当前节点的规则链表
lists := make([]*list.List, 0)
2018-07-30 13:50:48 +08:00
array := ([]string)(nil)
if strings.EqualFold("/", uri) {
array = []string{"/"}
} else {
array = strings.Split(uri[1:], "/")
}
2018-07-29 22:01:29 +08:00
// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
for k, v := range array {
if len(v) == 0 {
continue
2018-04-13 15:19:31 +08:00
}
2018-07-29 22:01:29 +08:00
// 判断是否模糊匹配规则
2018-08-16 18:17:47 +08:00
if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, v) {
2018-07-29 22:01:29 +08:00
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))
}
2018-07-29 22:01:29 +08:00
}
// 属性层级数据写入
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))
2018-04-13 15:19:31 +08:00
}
}
2018-07-29 22:01:29 +08:00
}
// 得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)
// 从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在前面)
2018-08-02 15:23:14 +08:00
item := (*handlerItem)(nil)
for _, l := range lists {
pushed := false
for e := l.Front(); e != nil; e = e.Next() {
item = e.Value.(*handlerItem)
// 判断是否已存在相同的路由注册项,是则进行替换
2018-08-02 15:23:14 +08:00
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
2018-07-29 22:01:29 +08:00
break
2018-07-27 19:03:32 +08:00
}
2018-07-29 22:01:29 +08:00
}
2018-08-02 15:23:14 +08:00
if s.compareRouterPriority(handler.router, item.router) {
l.InsertBefore(handler, e)
pushed = true
break
2018-04-13 15:19:31 +08:00
}
}
2018-08-02 15:23:14 +08:00
if !pushed {
l.PushBack(handler)
}
2018-04-13 15:19:31 +08:00
}
//gutil.Dump(s.serveTree)
//gutil.Dump(s.hooksTree)
2018-04-13 15:19:31 +08:00
return nil
}
// 对比两个handlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序。
// 优先级比较规则:
// 1、层级越深优先级越高(对比/数量)
// 2、模糊规则优先级{xxx} > :xxx > *xxx
2018-07-27 19:03:32 +08:00
func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
if newRouter.Priority > oldRouter.Priority {
2018-04-13 15:19:31 +08:00
return true
}
2018-07-27 19:03:32 +08:00
if newRouter.Priority < oldRouter.Priority {
2018-04-13 15:19:31 +08:00
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++
}
2018-04-13 15:19:31 +08:00
}
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 true
2018-04-13 15:19:31 +08:00
}
// 将pattern不带method和domain解析成正则表达式匹配以及对应的query字符串
2018-07-26 10:26:34 +08:00
func (s *Server) patternToRegRule(rule string) (regrule string, names []string) {
2018-04-13 15:19:31 +08:00
if len(rule) < 2 {
2018-07-26 10:26:34 +08:00
return rule, nil
2018-04-13 15:19:31 +08:00
}
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
2018-04-13 15:19:31 +08:00
case '*':
if len(v) > 1 {
regrule += `/{0,1}(.*)`
names = append(names, v[1:])
break
} else {
regrule += `/{0,1}.*`
break
}
fallthrough
2018-04-13 15:19:31 +08:00
default:
2018-08-16 18:17:47 +08:00
// 特殊字符替换
v = gstr.ReplaceByMap(v, map[string]string{
`.` : `\.`,
`+` : `\+`,
`*` : `.*`,
})
s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
2018-07-26 10:26:34 +08:00
names = append(names, s[1 : len(s) - 1])
return `(.+)`
})
if strings.EqualFold(s, v) {
regrule += "/" + v
} else {
regrule += "/" + s
}
2018-04-13 15:19:31 +08:00
}
}
regrule += `$`
return
}