github.com/zhongdalu/gf@v1.0.0/g/net/ghttp/ghttp_server_router.go (about)

     1  // Copyright 2018 gf Author(https://github.com/zhongdalu/gf). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/zhongdalu/gf.
     6  // 路由控制基本方法.
     7  
     8  package ghttp
     9  
    10  import (
    11  	"container/list"
    12  	"errors"
    13  	"fmt"
    14  	"github.com/zhongdalu/gf/g/os/glog"
    15  	"github.com/zhongdalu/gf/g/text/gregex"
    16  	"github.com/zhongdalu/gf/g/text/gstr"
    17  	"runtime"
    18  	"strings"
    19  )
    20  
    21  // 解析pattern
    22  func (s *Server) parsePattern(pattern string) (domain, method, path string, err error) {
    23  	path = strings.TrimSpace(pattern)
    24  	domain = gDEFAULT_DOMAIN
    25  	method = gDEFAULT_METHOD
    26  	if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
    27  		path = strings.TrimSpace(array[2])
    28  		if v := strings.TrimSpace(array[1]); v != "" {
    29  			method = v
    30  		}
    31  	}
    32  	if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, path); len(array) > 1 && err == nil {
    33  		path = strings.TrimSpace(array[1])
    34  		if v := strings.TrimSpace(array[2]); v != "" {
    35  			domain = v
    36  		}
    37  	}
    38  	if path == "" {
    39  		err = errors.New("invalid pattern: URI should not be empty")
    40  	}
    41  	// 去掉末尾的"/"符号,与路由匹配时处理一致
    42  	if path != "/" {
    43  		path = strings.TrimRight(path, "/")
    44  	}
    45  	return
    46  }
    47  
    48  // 获得服务注册的文件地址信息
    49  func (s *Server) getHandlerRegisterCallerLine(handler *handlerItem) string {
    50  	skip := 5
    51  	if handler.rtype == gROUTE_REGISTER_HANDLER {
    52  		skip = 4
    53  	}
    54  	if _, cfile, cline, ok := runtime.Caller(skip); ok {
    55  		return fmt.Sprintf("%s:%d", cfile, cline)
    56  	}
    57  	return ""
    58  }
    59  
    60  // 路由注册处理方法。
    61  // 如果带有hook参数,表示是回调注册方法; 否则为普通路由执行方法。
    62  func (s *Server) setHandler(pattern string, handler *handlerItem, hook ...string) {
    63  	// Web Server正常运行时无法动态注册路由方法
    64  	if s.Status() == SERVER_STATUS_RUNNING {
    65  		glog.Error("cannot bind handler while server running")
    66  		return
    67  	}
    68  	var hookName string
    69  	if len(hook) > 0 {
    70  		hookName = hook[0]
    71  	}
    72  	domain, method, uri, err := s.parsePattern(pattern)
    73  	if err != nil {
    74  		glog.Error("invalid pattern:", pattern, err)
    75  		return
    76  	}
    77  	if len(uri) == 0 || uri[0] != '/' {
    78  		glog.Error("invalid pattern:", pattern, "URI should lead with '/'")
    79  		return
    80  	}
    81  	// 注册地址记录及重复注册判断
    82  	regkey := s.handlerKey(hookName, method, uri, domain)
    83  	caller := s.getHandlerRegisterCallerLine(handler)
    84  	if len(hook) == 0 {
    85  		if item, ok := s.routesMap[regkey]; ok {
    86  			glog.Errorf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
    87  			return
    88  		}
    89  	}
    90  
    91  	// 路由对象
    92  	handler.router = &Router{
    93  		Uri:      uri,
    94  		Domain:   domain,
    95  		Method:   method,
    96  		Priority: strings.Count(uri[1:], "/"),
    97  	}
    98  	handler.router.RegRule, handler.router.RegNames = s.patternToRegRule(uri)
    99  
   100  	// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
   101  	// 非叶节点为哈希表检索节点,按照URI注册的层级进行高效检索,直至到叶子链表节点;
   102  	// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
   103  	tree := (map[string]interface{})(nil)
   104  	if len(hookName) == 0 {
   105  		tree = s.serveTree
   106  	} else {
   107  		tree = s.hooksTree
   108  	}
   109  	if _, ok := tree[domain]; !ok {
   110  		tree[domain] = make(map[string]interface{})
   111  	}
   112  	// 用于遍历的指针
   113  	p := tree[domain]
   114  	if len(hookName) > 0 {
   115  		if _, ok := p.(map[string]interface{})[hookName]; !ok {
   116  			p.(map[string]interface{})[hookName] = make(map[string]interface{})
   117  		}
   118  		p = p.(map[string]interface{})[hookName]
   119  	}
   120  	// 当前节点的规则链表
   121  	lists := make([]*list.List, 0)
   122  	array := ([]string)(nil)
   123  	if strings.EqualFold("/", uri) {
   124  		array = []string{"/"}
   125  	} else {
   126  		array = strings.Split(uri[1:], "/")
   127  	}
   128  	// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
   129  	// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
   130  	for k, v := range array {
   131  		if len(v) == 0 {
   132  			continue
   133  		}
   134  		// 判断是否模糊匹配规则
   135  		if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, v) {
   136  			v = "*fuzz"
   137  			// 由于是模糊规则,因此这里会有一个*list,用以将后续的路由规则加进来,
   138  			// 检索会从叶子节点的链表往根节点按照优先级进行检索
   139  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   140  				p.(map[string]interface{})["*list"] = list.New()
   141  				lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
   142  			} else {
   143  				lists = append(lists, v.(*list.List))
   144  			}
   145  		}
   146  		// 属性层级数据写入
   147  		if _, ok := p.(map[string]interface{})[v]; !ok {
   148  			p.(map[string]interface{})[v] = make(map[string]interface{})
   149  		}
   150  		p = p.(map[string]interface{})[v]
   151  		// 到达叶子节点,往list中增加匹配规则(条件 v != "*fuzz" 是因为模糊节点的话在前面已经添加了*list链表)
   152  		if k == len(array)-1 && v != "*fuzz" {
   153  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   154  				p.(map[string]interface{})["*list"] = list.New()
   155  				lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
   156  			} else {
   157  				lists = append(lists, v.(*list.List))
   158  			}
   159  		}
   160  	}
   161  	// 上面循环后得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)。
   162  	// 下面从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在lists链表的前面)
   163  	item := (*handlerItem)(nil)
   164  	for _, l := range lists {
   165  		pushed := false
   166  		for e := l.Front(); e != nil; e = e.Next() {
   167  			item = e.Value.(*handlerItem)
   168  			// 判断是否已存在相同的路由注册项,(如果不是hook注册)是则进行替换
   169  			if len(hookName) == 0 {
   170  				if strings.EqualFold(handler.router.Domain, item.router.Domain) &&
   171  					strings.EqualFold(handler.router.Method, item.router.Method) &&
   172  					strings.EqualFold(handler.router.Uri, item.router.Uri) {
   173  					e.Value = handler
   174  					pushed = true
   175  					break
   176  				}
   177  			}
   178  			// 如果路由注册项不相等,那么判断优先级,决定插入顺序
   179  			if s.compareRouterPriority(handler.router, item.router) {
   180  				l.InsertBefore(handler, e)
   181  				pushed = true
   182  				break
   183  			}
   184  		}
   185  		if !pushed {
   186  			l.PushBack(handler)
   187  		}
   188  	}
   189  	// gutil.Dump(s.serveTree)
   190  	// gutil.Dump(s.hooksTree)
   191  	if _, ok := s.routesMap[regkey]; !ok {
   192  		s.routesMap[regkey] = make([]registeredRouteItem, 0)
   193  	}
   194  	s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem{
   195  		file:    caller,
   196  		handler: handler,
   197  	})
   198  }
   199  
   200  // 对比两个handlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序。
   201  // 返回值true表示newRouter优先级比oldRouter高,会被添加链表中oldRouter的前面;否则后面。
   202  // 优先级比较规则:
   203  // 1、层级越深优先级越高(对比/数量);
   204  // 2、模糊规则优先级:{xxx} > :xxx > *xxx;
   205  func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
   206  	// 优先比较层级,层级越深优先级越高
   207  	if newRouter.Priority > oldRouter.Priority {
   208  		return true
   209  	}
   210  	if newRouter.Priority < oldRouter.Priority {
   211  		return false
   212  	}
   213  	// 精准匹配比模糊匹配规则优先级高,例如:/name/act 比 /{name}/:act 优先级高
   214  	var fuzzyCountFieldNew, fuzzyCountFieldOld int
   215  	var fuzzyCountNameNew, fuzzyCountNameOld int
   216  	var fuzzyCountAnyNew, fuzzyCountAnyOld int
   217  	var fuzzyCountTotalNew, fuzzyCountTotalOld int
   218  	for _, v := range newRouter.Uri {
   219  		switch v {
   220  		case '{':
   221  			fuzzyCountFieldNew++
   222  		case ':':
   223  			fuzzyCountNameNew++
   224  		case '*':
   225  			fuzzyCountAnyNew++
   226  		}
   227  	}
   228  	for _, v := range oldRouter.Uri {
   229  		switch v {
   230  		case '{':
   231  			fuzzyCountFieldOld++
   232  		case ':':
   233  			fuzzyCountNameOld++
   234  		case '*':
   235  			fuzzyCountAnyOld++
   236  		}
   237  	}
   238  	fuzzyCountTotalNew = fuzzyCountFieldNew + fuzzyCountNameNew + fuzzyCountAnyNew
   239  	fuzzyCountTotalOld = fuzzyCountFieldOld + fuzzyCountNameOld + fuzzyCountAnyOld
   240  	if fuzzyCountTotalNew < fuzzyCountTotalOld {
   241  		return true
   242  	}
   243  	if fuzzyCountTotalNew > fuzzyCountTotalOld {
   244  		return false
   245  	}
   246  
   247  	/** 如果模糊规则数量相等,那么执行分别的数量判断 **/
   248  
   249  	// 例如:/name/{act} 比 /name/:act 优先级高
   250  	if fuzzyCountFieldNew > fuzzyCountFieldOld {
   251  		return true
   252  	}
   253  	if fuzzyCountFieldNew < fuzzyCountFieldOld {
   254  		return false
   255  	}
   256  	// 例如: /name/:act 比 /name/*act 优先级高
   257  	if fuzzyCountNameNew > fuzzyCountNameOld {
   258  		return true
   259  	}
   260  	if fuzzyCountNameNew < fuzzyCountNameOld {
   261  		return false
   262  	}
   263  
   264  	/* 模糊规则数量相等,后续不用再判断*规则的数量比较了 */
   265  
   266  	// 比较HTTP METHOD,更精准的优先级更高
   267  	if newRouter.Method != gDEFAULT_METHOD {
   268  		return true
   269  	}
   270  	if oldRouter.Method != gDEFAULT_METHOD {
   271  		return true
   272  	}
   273  
   274  	// 最后新的规则比旧的规则优先级低
   275  	return false
   276  }
   277  
   278  // 将pattern(不带method和domain)解析成正则表达式匹配以及对应的query字符串
   279  func (s *Server) patternToRegRule(rule string) (regrule string, names []string) {
   280  	if len(rule) < 2 {
   281  		return rule, nil
   282  	}
   283  	regrule = "^"
   284  	array := strings.Split(rule[1:], "/")
   285  	for _, v := range array {
   286  		if len(v) == 0 {
   287  			continue
   288  		}
   289  		switch v[0] {
   290  		case ':':
   291  			if len(v) > 1 {
   292  				regrule += `/([^/]+)`
   293  				names = append(names, v[1:])
   294  				break
   295  			} else {
   296  				regrule += `/[^/]+`
   297  				break
   298  			}
   299  		case '*':
   300  			if len(v) > 1 {
   301  				regrule += `/{0,1}(.*)`
   302  				names = append(names, v[1:])
   303  				break
   304  			} else {
   305  				regrule += `/{0,1}.*`
   306  				break
   307  			}
   308  		default:
   309  			// 特殊字符替换
   310  			v = gstr.ReplaceByMap(v, map[string]string{
   311  				`.`: `\.`,
   312  				`+`: `\+`,
   313  				`*`: `.*`,
   314  			})
   315  			s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
   316  				names = append(names, s[1:len(s)-1])
   317  				return `([^/]+)`
   318  			})
   319  			if strings.EqualFold(s, v) {
   320  				regrule += "/" + v
   321  			} else {
   322  				regrule += "/" + s
   323  			}
   324  		}
   325  	}
   326  	regrule += `$`
   327  	return
   328  }