github.com/gogf/gf/v2@v2.7.4/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  	"context"
    11  	"fmt"
    12  	"reflect"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/gogf/gf/v2/container/glist"
    17  	"github.com/gogf/gf/v2/container/gtype"
    18  	"github.com/gogf/gf/v2/debug/gdebug"
    19  	"github.com/gogf/gf/v2/errors/gcode"
    20  	"github.com/gogf/gf/v2/errors/gerror"
    21  	"github.com/gogf/gf/v2/internal/consts"
    22  	"github.com/gogf/gf/v2/text/gregex"
    23  	"github.com/gogf/gf/v2/text/gstr"
    24  	"github.com/gogf/gf/v2/util/gmeta"
    25  	"github.com/gogf/gf/v2/util/gtag"
    26  )
    27  
    28  var (
    29  	// handlerIdGenerator is handler item id generator.
    30  	handlerIdGenerator = gtype.NewInt()
    31  )
    32  
    33  // routerMapKey creates and returns a 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 HookName, method, path, domain string) string {
    37  	return string(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  type setHandlerInput struct {
    67  	Prefix      string
    68  	Pattern     string
    69  	HandlerItem *HandlerItem
    70  }
    71  
    72  // setHandler creates router item with a given handler and pattern and registers the handler to the router tree.
    73  // The router tree can be treated as a multilayer hash table, please refer to the comment in the following codes.
    74  // This function is called during server starts up, which cares little about the performance. What really cares
    75  // is the well-designed router storage structure for router searching when the request is under serving.
    76  func (s *Server) setHandler(ctx context.Context, in setHandlerInput) {
    77  	var (
    78  		prefix  = in.Prefix
    79  		pattern = in.Pattern
    80  		handler = in.HandlerItem
    81  	)
    82  	if handler.Name == "" {
    83  		handler.Name = runtime.FuncForPC(handler.Info.Value.Pointer()).Name()
    84  	}
    85  	if handler.Source == "" {
    86  		_, file, line := gdebug.CallerWithFilter([]string{consts.StackFilterKeyForGoFrame})
    87  		handler.Source = fmt.Sprintf(`%s:%d`, file, line)
    88  	}
    89  	domain, method, uri, err := s.parsePattern(pattern)
    90  	if err != nil {
    91  		s.Logger().Fatalf(ctx, `invalid pattern "%s", %+v`, pattern, err)
    92  		return
    93  	}
    94  	// ====================================================================================
    95  	// Change the registered route according to meta info from its request structure.
    96  	// It supports multiple methods that are joined using char `,`.
    97  	// ====================================================================================
    98  	if handler.Info.Type != nil && handler.Info.Type.NumIn() == 2 {
    99  		var objectReq = reflect.New(handler.Info.Type.In(1))
   100  		if v := gmeta.Get(objectReq, gtag.Path); !v.IsEmpty() {
   101  			uri = v.String()
   102  		}
   103  		if v := gmeta.Get(objectReq, gtag.Domain); !v.IsEmpty() {
   104  			domain = v.String()
   105  		}
   106  		if v := gmeta.Get(objectReq, gtag.Method); !v.IsEmpty() {
   107  			method = v.String()
   108  		}
   109  		// Multiple methods registering, which are joined using char `,`.
   110  		if gstr.Contains(method, ",") {
   111  			methods := gstr.SplitAndTrim(method, ",")
   112  			for _, v := range methods {
   113  				// Each method has it own handler.
   114  				clonedHandler := *handler
   115  				s.doSetHandler(ctx, &clonedHandler, prefix, uri, pattern, v, domain)
   116  			}
   117  			return
   118  		}
   119  		// Converts `all` to `ALL`.
   120  		if gstr.Equal(method, defaultMethod) {
   121  			method = defaultMethod
   122  		}
   123  	}
   124  	s.doSetHandler(ctx, handler, prefix, uri, pattern, method, domain)
   125  }
   126  
   127  func (s *Server) doSetHandler(
   128  	ctx context.Context, handler *HandlerItem,
   129  	prefix, uri, pattern, method, domain string,
   130  ) {
   131  	if !s.isValidMethod(method) {
   132  		s.Logger().Fatalf(
   133  			ctx,
   134  			`invalid method value "%s", should be in "%s" or "%s"`,
   135  			method, supportedHttpMethods, defaultMethod,
   136  		)
   137  	}
   138  	// Prefix for URI feature.
   139  	if prefix != "" {
   140  		uri = prefix + "/" + strings.TrimLeft(uri, "/")
   141  	}
   142  	uri = strings.TrimRight(uri, "/")
   143  	if uri == "" {
   144  		uri = "/"
   145  	}
   146  
   147  	if len(uri) == 0 || uri[0] != '/' {
   148  		s.Logger().Fatalf(ctx, `invalid pattern "%s", URI should lead with '/'`, pattern)
   149  	}
   150  
   151  	// Repeated router checks, this feature can be disabled by server configuration.
   152  	var routerKey = s.routerMapKey(handler.HookName, method, uri, domain)
   153  	if !s.config.RouteOverWrite {
   154  		switch handler.Type {
   155  		case HandlerTypeHandler, HandlerTypeObject:
   156  			if items, ok := s.routesMap[routerKey]; ok {
   157  				var duplicatedHandler *HandlerItem
   158  				for i, item := range items {
   159  					switch item.Type {
   160  					case HandlerTypeHandler, HandlerTypeObject:
   161  						duplicatedHandler = items[i]
   162  					}
   163  					if duplicatedHandler != nil {
   164  						break
   165  					}
   166  				}
   167  				if duplicatedHandler != nil {
   168  					s.Logger().Fatalf(
   169  						ctx,
   170  						"The duplicated route registry [%s] which is meaning [{hook}%%{method}:{path}@{domain}] at \n%s -> %s , which has already been registered at \n%s -> %s"+
   171  							"\nYou can disable duplicate route detection by modifying the server.routeOverWrite configuration, but this will cause some routes to be overwritten",
   172  						routerKey, handler.Source, handler.Name, duplicatedHandler.Source, duplicatedHandler.Name,
   173  					)
   174  				}
   175  			}
   176  		}
   177  	}
   178  	// Unique id for each handler.
   179  	handler.Id = handlerIdGenerator.Add(1)
   180  	// Create a new router by given parameter.
   181  	handler.Router = &Router{
   182  		Uri:      uri,
   183  		Domain:   domain,
   184  		Method:   strings.ToUpper(method),
   185  		Priority: strings.Count(uri[1:], "/"),
   186  	}
   187  	handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri)
   188  
   189  	if _, ok := s.serveTree[domain]; !ok {
   190  		s.serveTree[domain] = make(map[string]interface{})
   191  	}
   192  	// List array, very important for router registering.
   193  	// There may be multiple lists adding into this array when searching from root to leaf.
   194  	var (
   195  		array []string
   196  		lists = make([]*glist.List, 0)
   197  	)
   198  	if strings.EqualFold("/", uri) {
   199  		array = []string{"/"}
   200  	} else {
   201  		array = strings.Split(uri[1:], "/")
   202  	}
   203  	// Multilayer hash table:
   204  	// 1. Each node of the table is separated by URI path which is split by char '/'.
   205  	// 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name.
   206  	// 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM,
   207  	//    especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item,
   208  	//    and the leaf node also has "*list" item. If the node is not a fuzzy node either
   209  	//    a leaf, it neither has "*list" item.
   210  	// 2. The "*list" item is a list containing registered router items ordered by their
   211  	//    priorities from high to low. If it's a fuzzy node, all the sub router items
   212  	//    from this fuzzy node will also be added to its "*list" item.
   213  	// 3. There may be repeated router items in the router lists. The lists' priorities
   214  	//    from root to leaf are from low to high.
   215  	var p = s.serveTree[domain]
   216  	for i, part := range array {
   217  		// Ignore empty URI part, like: /user//index
   218  		if part == "" {
   219  			continue
   220  		}
   221  		// Check if it's a fuzzy node.
   222  		if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) {
   223  			part = "*fuzz"
   224  			// If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map.
   225  			// All the sub router items from this fuzzy node will also be added to its "*list" item.
   226  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   227  				newListForFuzzy := glist.New()
   228  				p.(map[string]interface{})["*list"] = newListForFuzzy
   229  				lists = append(lists, newListForFuzzy)
   230  			} else {
   231  				lists = append(lists, v.(*glist.List))
   232  			}
   233  		}
   234  		// Make a new bucket for the current node.
   235  		if _, ok := p.(map[string]interface{})[part]; !ok {
   236  			p.(map[string]interface{})[part] = make(map[string]interface{})
   237  		}
   238  		// Loop to next bucket.
   239  		p = p.(map[string]interface{})[part]
   240  		// The leaf is a hash map and must have an item named "*list", which contains the router item.
   241  		// The leaf can be furthermore extended by adding more ket-value pairs into its map.
   242  		// Note that the `v != "*fuzz"` comparison is required as the list might be added in the former
   243  		// fuzzy checks.
   244  		if i == len(array)-1 && part != "*fuzz" {
   245  			if v, ok := p.(map[string]interface{})["*list"]; !ok {
   246  				leafList := glist.New()
   247  				p.(map[string]interface{})["*list"] = leafList
   248  				lists = append(lists, leafList)
   249  			} else {
   250  				lists = append(lists, v.(*glist.List))
   251  			}
   252  		}
   253  	}
   254  	// It iterates the list array of `lists`, compares priorities and inserts the new router item in
   255  	// the proper position of each list. The priority of the list is ordered from high to low.
   256  	var item *HandlerItem
   257  	for _, l := range lists {
   258  		pushed := false
   259  		for e := l.Front(); e != nil; e = e.Next() {
   260  			item = e.Value.(*HandlerItem)
   261  			// Checks the priority whether inserting the route item before current item,
   262  			// which means it has higher priority.
   263  			if s.compareRouterPriority(handler, item) {
   264  				l.InsertBefore(e, handler)
   265  				pushed = true
   266  				goto end
   267  			}
   268  		}
   269  	end:
   270  		// Just push back in default.
   271  		if !pushed {
   272  			l.PushBack(handler)
   273  		}
   274  	}
   275  	// Initialize the route map item.
   276  	if _, ok := s.routesMap[routerKey]; !ok {
   277  		s.routesMap[routerKey] = make([]*HandlerItem, 0)
   278  	}
   279  
   280  	// Append the route.
   281  	s.routesMap[routerKey] = append(s.routesMap[routerKey], handler)
   282  }
   283  
   284  func (s *Server) isValidMethod(method string) bool {
   285  	if gstr.Equal(method, defaultMethod) {
   286  		return true
   287  	}
   288  	_, ok := methodsMap[strings.ToUpper(method)]
   289  	return ok
   290  }
   291  
   292  // compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true
   293  // if `newItem`'s priority is higher than `oldItem`, else it returns false. The higher priority
   294  // item will be inserted into the router list before the other one.
   295  //
   296  // Comparison rules:
   297  // 1. The middleware has the most high priority.
   298  // 2. URI: The deeper, the higher (simply check the count of char '/' in the URI).
   299  // 3. Route type: {xxx} > :xxx > *xxx.
   300  func (s *Server) compareRouterPriority(newItem *HandlerItem, oldItem *HandlerItem) bool {
   301  	// If they're all types of middleware, the priority is according to their registered sequence.
   302  	if newItem.Type == HandlerTypeMiddleware && oldItem.Type == HandlerTypeMiddleware {
   303  		return false
   304  	}
   305  	// The middleware has the most high priority.
   306  	if newItem.Type == HandlerTypeMiddleware && oldItem.Type != HandlerTypeMiddleware {
   307  		return true
   308  	}
   309  	// URI: The deeper, the higher (simply check the count of char '/' in the URI).
   310  	if newItem.Router.Priority > oldItem.Router.Priority {
   311  		return true
   312  	}
   313  	if newItem.Router.Priority < oldItem.Router.Priority {
   314  		return false
   315  	}
   316  
   317  	// Compare the length of their URI,
   318  	// but the fuzzy and named parts of the URI are not calculated to the result.
   319  
   320  	// Example:
   321  	// /admin-goods-{page} > /admin-{page}
   322  	// /{hash}.{type}      > /{hash}
   323  	var uriNew, uriOld string
   324  	uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.Router.Uri)
   325  	uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.Router.Uri)
   326  	uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew)
   327  	uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld)
   328  	uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any".
   329  	uriOld, _ = gregex.ReplaceString(`\*[^/]*`, "", uriOld) // Replace "/*" and "/*any".
   330  	if len(uriNew) > len(uriOld) {
   331  		return true
   332  	}
   333  	if len(uriNew) < len(uriOld) {
   334  		return false
   335  	}
   336  
   337  	// Route type checks: {xxx} > :xxx > *xxx.
   338  	// Example:
   339  	// /name/act > /{name}/:act
   340  	var (
   341  		fuzzyCountFieldNew int
   342  		fuzzyCountFieldOld int
   343  		fuzzyCountNameNew  int
   344  		fuzzyCountNameOld  int
   345  		fuzzyCountAnyNew   int
   346  		fuzzyCountAnyOld   int
   347  		fuzzyCountTotalNew int
   348  		fuzzyCountTotalOld int
   349  	)
   350  	for _, v := range newItem.Router.Uri {
   351  		switch v {
   352  		case '{':
   353  			fuzzyCountFieldNew++
   354  		case ':':
   355  			fuzzyCountNameNew++
   356  		case '*':
   357  			fuzzyCountAnyNew++
   358  		}
   359  	}
   360  	for _, v := range oldItem.Router.Uri {
   361  		switch v {
   362  		case '{':
   363  			fuzzyCountFieldOld++
   364  		case ':':
   365  			fuzzyCountNameOld++
   366  		case '*':
   367  			fuzzyCountAnyOld++
   368  		}
   369  	}
   370  	fuzzyCountTotalNew = fuzzyCountFieldNew + fuzzyCountNameNew + fuzzyCountAnyNew
   371  	fuzzyCountTotalOld = fuzzyCountFieldOld + fuzzyCountNameOld + fuzzyCountAnyOld
   372  	if fuzzyCountTotalNew < fuzzyCountTotalOld {
   373  		return true
   374  	}
   375  	if fuzzyCountTotalNew > fuzzyCountTotalOld {
   376  		return false
   377  	}
   378  
   379  	// If the counts of their fuzzy rules are equal.
   380  
   381  	// Eg: /name/{act} > /name/:act
   382  	if fuzzyCountFieldNew > fuzzyCountFieldOld {
   383  		return true
   384  	}
   385  	if fuzzyCountFieldNew < fuzzyCountFieldOld {
   386  		return false
   387  	}
   388  	// Eg: /name/:act > /name/*act
   389  	if fuzzyCountNameNew > fuzzyCountNameOld {
   390  		return true
   391  	}
   392  	if fuzzyCountNameNew < fuzzyCountNameOld {
   393  		return false
   394  	}
   395  
   396  	// It then compares the accuracy of their http method,
   397  	// the more accurate the more priority.
   398  	if newItem.Router.Method != defaultMethod {
   399  		return true
   400  	}
   401  	if oldItem.Router.Method != defaultMethod {
   402  		return true
   403  	}
   404  
   405  	// If they have different router type,
   406  	// the new router item has more priority than the other one.
   407  	if newItem.Type == HandlerTypeHandler || newItem.Type == HandlerTypeObject {
   408  		return true
   409  	}
   410  
   411  	// Other situations, like HOOK items,
   412  	// the old router item has more priority than the other one.
   413  	return false
   414  }
   415  
   416  // patternToRegular converts route rule to according to regular expression.
   417  func (s *Server) patternToRegular(rule string) (regular string, names []string) {
   418  	if len(rule) < 2 {
   419  		return rule, nil
   420  	}
   421  	regular = "^"
   422  	var array = strings.Split(rule[1:], "/")
   423  	for _, v := range array {
   424  		if len(v) == 0 {
   425  			continue
   426  		}
   427  		switch v[0] {
   428  		case ':':
   429  			if len(v) > 1 {
   430  				regular += `/([^/]+)`
   431  				names = append(names, v[1:])
   432  			} else {
   433  				regular += `/[^/]+`
   434  			}
   435  		case '*':
   436  			if len(v) > 1 {
   437  				regular += `/{0,1}(.*)`
   438  				names = append(names, v[1:])
   439  			} else {
   440  				regular += `/{0,1}.*`
   441  			}
   442  		default:
   443  			// Special chars replacement.
   444  			v = gstr.ReplaceByMap(v, map[string]string{
   445  				`.`: `\.`,
   446  				`+`: `\+`,
   447  				`*`: `.*`,
   448  			})
   449  			s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
   450  				names = append(names, s[1:len(s)-1])
   451  				return `([^/]+)`
   452  			})
   453  			if strings.EqualFold(s, v) {
   454  				regular += "/" + v
   455  			} else {
   456  				regular += "/" + s
   457  			}
   458  		}
   459  	}
   460  	regular += `$`
   461  	return
   462  }