github.com/gogf/gf@v1.16.9/net/ghttp/ghttp_server_router.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). 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/gogf/gf.
     6  
     7  package ghttp
     8  
     9  import (
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/gogf/gf/container/gtype"
    14  	"github.com/gogf/gf/errors/gcode"
    15  	"github.com/gogf/gf/errors/gerror"
    16  
    17  	"github.com/gogf/gf/debug/gdebug"
    18  
    19  	"github.com/gogf/gf/container/glist"
    20  	"github.com/gogf/gf/text/gregex"
    21  	"github.com/gogf/gf/text/gstr"
    22  )
    23  
    24  const (
    25  	stackFilterKey = "/net/ghttp/ghttp"
    26  )
    27  
    28  var (
    29  	// handlerIdGenerator is handler item id generator.
    30  	handlerIdGenerator = gtype.NewInt()
    31  )
    32  
    33  // routerMapKey creates and returns an unique router key for given parameters.
    34  // This key is used for Server.routerMap attribute, which is mainly for checks for
    35  // repeated router registering.
    36  func (s *Server) routerMapKey(hook, method, path, domain string) string {
    37  	return hook + "%" + s.serveHandlerKey(method, path, domain)
    38  }
    39  
    40  // parsePattern parses the given pattern to domain, method and path variable.
    41  func (s *Server) parsePattern(pattern string) (domain, method, path string, err error) {
    42  	path = strings.TrimSpace(pattern)
    43  	domain = defaultDomainName
    44  	method = defaultMethod
    45  	if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
    46  		path = strings.TrimSpace(array[2])
    47  		if v := strings.TrimSpace(array[1]); v != "" {
    48  			method = v
    49  		}
    50  	}
    51  	if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, path); len(array) > 1 && err == nil {
    52  		path = strings.TrimSpace(array[1])
    53  		if v := strings.TrimSpace(array[2]); v != "" {
    54  			domain = v
    55  		}
    56  	}
    57  	if path == "" {
    58  		err = gerror.NewCode(gcode.CodeInvalidParameter, "invalid pattern: URI should not be empty")
    59  	}
    60  	if path != "/" {
    61  		path = strings.TrimRight(path, "/")
    62  	}
    63  	return
    64  }
    65  
    66  // setHandler creates router item with given handler and pattern and registers the handler to the router tree.
    67  // The router tree can be treated as a multilayer hash table, please refer to the comment in following codes.
    68  // This function is called during server starts up, which cares little about the performance. What really cares
    69  // is the well designed router storage structure for router searching when the request is under serving.
    70  func (s *Server) setHandler(pattern string, handler *handlerItem) {
    71  	handler.Id = handlerIdGenerator.Add(1)
    72  	if handler.Source == "" {
    73  		_, file, line := gdebug.CallerWithFilter([]string{stackFilterKey})
    74  		handler.Source = fmt.Sprintf(`%s:%d`, file, line)
    75  	}
    76  	domain, method, uri, err := s.parsePattern(pattern)
    77  	if err != nil {
    78  		s.Logger().Fatal("invalid pattern:", pattern, err)
    79  		return
    80  	}
    81  	if len(uri) == 0 || uri[0] != '/' {
    82  		s.Logger().Fatal("invalid pattern:", pattern, "URI should lead with '/'")
    83  		return
    84  	}
    85  
    86  	// Repeated router checks, this feature can be disabled by server configuration.
    87  	routerKey := s.routerMapKey(handler.HookName, method, uri, domain)
    88  	if !s.config.RouteOverWrite {
    89  		switch handler.Type {
    90  		case handlerTypeHandler, handlerTypeObject, handlerTypeController:
    91  			if item, ok := s.routesMap[routerKey]; ok {
    92  				s.Logger().Fatalf(
    93  					`duplicated route registry "%s" at %s , already registered at %s`,
    94  					pattern, handler.Source, item[0].Source,
    95  				)
    96  				return
    97  			}
    98  		}
    99  	}
   100  	// Create a new router by given parameter.
   101  	handler.Router = &Router{
   102  		Uri:      uri,
   103  		Domain:   domain,
   104  		Method:   strings.ToUpper(method),
   105  		Priority: strings.Count(uri[1:], "/"),
   106  	}
   107  	handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri)
   108  
   109  	if _, ok := s.serveTree[domain]; !ok {
   110  		s.serveTree[domain] = make(map[string]interface{})
   111  	}
   112  	// List array, very important for router registering.
   113  	// There may be multiple lists adding into this array when searching from root to leaf.
   114  	lists := make([]*glist.List, 0)
   115  	array := ([]string)(nil)
   116  	if strings.EqualFold("/", uri) {
   117  		array = []string{"/"}
   118  	} else {
   119  		array = strings.Split(uri[1:], "/")
   120  	}
   121  	// Multilayer hash table:
   122  	// 1. Each node of the table is separated by URI path which is split by char '/'.
   123  	// 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name.
   124  	// 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM,
   125  	//    especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item,
   126  	//    and the leaf node also has "*list" item. If the node is not a fuzzy node either
   127  	//    a leaf, it neither has "*list" item.
   128  	// 2. The "*list" item is a list containing registered router items ordered by their
   129  	//    priorities from high to low.
   130  	// 3. There may be repeated router items in the router lists. The lists' priorities
   131  	//    from root to leaf are from low to high.
   132  	p := s.serveTree[domain]
   133  	for i, part := range array {
   134  		// Ignore empty URI part, like: /user//index
   135  		if part == "" {
   136  			continue
   137  		}
   138  		// Check if it's a fuzzy node.
   139  		if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) {
   140  			part = "*fuzz"
   141  			// If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map.
   142  			// All the sub router items from this fuzzy node will also be added to its "*list" item.
   143  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   144  				newListForFuzzy := glist.New()
   145  				p.(map[string]interface{})["*list"] = newListForFuzzy
   146  				lists = append(lists, newListForFuzzy)
   147  			} else {
   148  				lists = append(lists, v.(*glist.List))
   149  			}
   150  		}
   151  		// Make a new bucket for current node.
   152  		if _, ok := p.(map[string]interface{})[part]; !ok {
   153  			p.(map[string]interface{})[part] = make(map[string]interface{})
   154  		}
   155  		// Loop to next bucket.
   156  		p = p.(map[string]interface{})[part]
   157  		// The leaf is a hash map and must have an item named "*list", which contains the router item.
   158  		// The leaf can be furthermore extended by adding more ket-value pairs into its map.
   159  		// Note that the `v != "*fuzz"` comparison is required as the list might be added in the former
   160  		// fuzzy checks.
   161  		if i == len(array)-1 && part != "*fuzz" {
   162  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   163  				leafList := glist.New()
   164  				p.(map[string]interface{})["*list"] = leafList
   165  				lists = append(lists, leafList)
   166  			} else {
   167  				lists = append(lists, v.(*glist.List))
   168  			}
   169  		}
   170  	}
   171  	// It iterates the list array of <lists>, compares priorities and inserts the new router item in
   172  	// the proper position of each list. The priority of the list is ordered from high to low.
   173  	item := (*handlerItem)(nil)
   174  	for _, l := range lists {
   175  		pushed := false
   176  		for e := l.Front(); e != nil; e = e.Next() {
   177  			item = e.Value.(*handlerItem)
   178  			// Checks the priority whether inserting the route item before current item,
   179  			// which means it has more higher priority.
   180  			if s.compareRouterPriority(handler, item) {
   181  				l.InsertBefore(e, handler)
   182  				pushed = true
   183  				goto end
   184  			}
   185  		}
   186  	end:
   187  		// Just push back in default.
   188  		if !pushed {
   189  			l.PushBack(handler)
   190  		}
   191  	}
   192  	// Initialize the route map item.
   193  	if _, ok := s.routesMap[routerKey]; !ok {
   194  		s.routesMap[routerKey] = make([]registeredRouteItem, 0)
   195  	}
   196  
   197  	routeItem := registeredRouteItem{
   198  		Source:  handler.Source,
   199  		Handler: handler,
   200  	}
   201  	switch handler.Type {
   202  	case handlerTypeHandler, handlerTypeObject, handlerTypeController:
   203  		// Overwrite the route.
   204  		s.routesMap[routerKey] = []registeredRouteItem{routeItem}
   205  	default:
   206  		// Append the route.
   207  		s.routesMap[routerKey] = append(s.routesMap[routerKey], routeItem)
   208  	}
   209  }
   210  
   211  // compareRouterPriority compares the priority between <newItem> and <oldItem>. It returns true
   212  // if <newItem>'s priority is higher than <oldItem>, else it returns false. The higher priority
   213  // item will be insert into the router list before the other one.
   214  //
   215  // Comparison rules:
   216  // 1. The middleware has the most high priority.
   217  // 2. URI: The deeper the higher (simply check the count of char '/' in the URI).
   218  // 3. Route type: {xxx} > :xxx > *xxx.
   219  func (s *Server) compareRouterPriority(newItem *handlerItem, oldItem *handlerItem) bool {
   220  	// If they're all type of middleware, the priority is according their registered sequence.
   221  	if newItem.Type == handlerTypeMiddleware && oldItem.Type == handlerTypeMiddleware {
   222  		return false
   223  	}
   224  	// The middleware has the most high priority.
   225  	if newItem.Type == handlerTypeMiddleware && oldItem.Type != handlerTypeMiddleware {
   226  		return true
   227  	}
   228  	// URI: The deeper the higher (simply check the count of char '/' in the URI).
   229  	if newItem.Router.Priority > oldItem.Router.Priority {
   230  		return true
   231  	}
   232  	if newItem.Router.Priority < oldItem.Router.Priority {
   233  		return false
   234  	}
   235  
   236  	// Compare the length of their URI,
   237  	// but the fuzzy and named parts of the URI are not calculated to the result.
   238  
   239  	// Example:
   240  	// /admin-goods-{page} > /admin-{page}
   241  	// /{hash}.{type}      > /{hash}
   242  	var uriNew, uriOld string
   243  	uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.Router.Uri)
   244  	uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.Router.Uri)
   245  	uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew)
   246  	uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld)
   247  	uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any".
   248  	uriOld, _ = gregex.ReplaceString(`\*[^/]*`, "", uriOld) // Replace "/*" and "/*any".
   249  	if len(uriNew) > len(uriOld) {
   250  		return true
   251  	}
   252  	if len(uriNew) < len(uriOld) {
   253  		return false
   254  	}
   255  
   256  	// Route type checks: {xxx} > :xxx > *xxx.
   257  	// Example:
   258  	// /name/act > /{name}/:act
   259  	var (
   260  		fuzzyCountFieldNew int
   261  		fuzzyCountFieldOld int
   262  		fuzzyCountNameNew  int
   263  		fuzzyCountNameOld  int
   264  		fuzzyCountAnyNew   int
   265  		fuzzyCountAnyOld   int
   266  		fuzzyCountTotalNew int
   267  		fuzzyCountTotalOld int
   268  	)
   269  	for _, v := range newItem.Router.Uri {
   270  		switch v {
   271  		case '{':
   272  			fuzzyCountFieldNew++
   273  		case ':':
   274  			fuzzyCountNameNew++
   275  		case '*':
   276  			fuzzyCountAnyNew++
   277  		}
   278  	}
   279  	for _, v := range oldItem.Router.Uri {
   280  		switch v {
   281  		case '{':
   282  			fuzzyCountFieldOld++
   283  		case ':':
   284  			fuzzyCountNameOld++
   285  		case '*':
   286  			fuzzyCountAnyOld++
   287  		}
   288  	}
   289  	fuzzyCountTotalNew = fuzzyCountFieldNew + fuzzyCountNameNew + fuzzyCountAnyNew
   290  	fuzzyCountTotalOld = fuzzyCountFieldOld + fuzzyCountNameOld + fuzzyCountAnyOld
   291  	if fuzzyCountTotalNew < fuzzyCountTotalOld {
   292  		return true
   293  	}
   294  	if fuzzyCountTotalNew > fuzzyCountTotalOld {
   295  		return false
   296  	}
   297  
   298  	// If the counts of their fuzzy rules equal.
   299  
   300  	// Eg: /name/{act} > /name/:act
   301  	if fuzzyCountFieldNew > fuzzyCountFieldOld {
   302  		return true
   303  	}
   304  	if fuzzyCountFieldNew < fuzzyCountFieldOld {
   305  		return false
   306  	}
   307  	// Eg: /name/:act > /name/*act
   308  	if fuzzyCountNameNew > fuzzyCountNameOld {
   309  		return true
   310  	}
   311  	if fuzzyCountNameNew < fuzzyCountNameOld {
   312  		return false
   313  	}
   314  
   315  	// It then compares the accuracy of their http method,
   316  	// the more accurate the more priority.
   317  	if newItem.Router.Method != defaultMethod {
   318  		return true
   319  	}
   320  	if oldItem.Router.Method != defaultMethod {
   321  		return true
   322  	}
   323  
   324  	// If they have different router type,
   325  	// the new router item has more priority than the other one.
   326  	if newItem.Type == handlerTypeHandler || newItem.Type == handlerTypeObject || newItem.Type == handlerTypeController {
   327  		return true
   328  	}
   329  
   330  	// Other situations, like HOOK items,
   331  	// the old router item has more priority than the other one.
   332  	return false
   333  }
   334  
   335  // patternToRegular converts route rule to according regular expression.
   336  func (s *Server) patternToRegular(rule string) (regular string, names []string) {
   337  	if len(rule) < 2 {
   338  		return rule, nil
   339  	}
   340  	regular = "^"
   341  	array := strings.Split(rule[1:], "/")
   342  	for _, v := range array {
   343  		if len(v) == 0 {
   344  			continue
   345  		}
   346  		switch v[0] {
   347  		case ':':
   348  			if len(v) > 1 {
   349  				regular += `/([^/]+)`
   350  				names = append(names, v[1:])
   351  			} else {
   352  				regular += `/[^/]+`
   353  			}
   354  		case '*':
   355  			if len(v) > 1 {
   356  				regular += `/{0,1}(.*)`
   357  				names = append(names, v[1:])
   358  			} else {
   359  				regular += `/{0,1}.*`
   360  			}
   361  		default:
   362  			// Special chars replacement.
   363  			v = gstr.ReplaceByMap(v, map[string]string{
   364  				`.`: `\.`,
   365  				`+`: `\+`,
   366  				`*`: `.*`,
   367  			})
   368  			s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
   369  				names = append(names, s[1:len(s)-1])
   370  				return `([^/]+)`
   371  			})
   372  			if strings.EqualFold(s, v) {
   373  				regular += "/" + v
   374  			} else {
   375  				regular += "/" + s
   376  			}
   377  		}
   378  	}
   379  	regular += `$`
   380  	return
   381  }