github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/gf. 6 7 package ghttp 8 9 import ( 10 "context" 11 "fmt" 12 "reflect" 13 "runtime" 14 "strings" 15 16 "github.com/wangyougui/gf/v2/container/glist" 17 "github.com/wangyougui/gf/v2/container/gtype" 18 "github.com/wangyougui/gf/v2/debug/gdebug" 19 "github.com/wangyougui/gf/v2/errors/gcode" 20 "github.com/wangyougui/gf/v2/errors/gerror" 21 "github.com/wangyougui/gf/v2/internal/consts" 22 "github.com/wangyougui/gf/v2/text/gregex" 23 "github.com/wangyougui/gf/v2/text/gstr" 24 "github.com/wangyougui/gf/v2/util/gmeta" 25 "github.com/wangyougui/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 `duplicated route registry "%s" at %s , already registered at %s`, 171 pattern, handler.Source, duplicatedHandler.Source, 172 ) 173 } 174 } 175 } 176 } 177 // Unique id for each handler. 178 handler.Id = handlerIdGenerator.Add(1) 179 // Create a new router by given parameter. 180 handler.Router = &Router{ 181 Uri: uri, 182 Domain: domain, 183 Method: strings.ToUpper(method), 184 Priority: strings.Count(uri[1:], "/"), 185 } 186 handler.Router.RegRule, handler.Router.RegNames = s.patternToRegular(uri) 187 188 if _, ok := s.serveTree[domain]; !ok { 189 s.serveTree[domain] = make(map[string]interface{}) 190 } 191 // List array, very important for router registering. 192 // There may be multiple lists adding into this array when searching from root to leaf. 193 var ( 194 array []string 195 lists = make([]*glist.List, 0) 196 ) 197 if strings.EqualFold("/", uri) { 198 array = []string{"/"} 199 } else { 200 array = strings.Split(uri[1:], "/") 201 } 202 // Multilayer hash table: 203 // 1. Each node of the table is separated by URI path which is split by char '/'. 204 // 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name. 205 // 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM, 206 // especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item, 207 // and the leaf node also has "*list" item. If the node is not a fuzzy node either 208 // a leaf, it neither has "*list" item. 209 // 2. The "*list" item is a list containing registered router items ordered by their 210 // priorities from high to low. If it's a fuzzy node, all the sub router items 211 // from this fuzzy node will also be added to its "*list" item. 212 // 3. There may be repeated router items in the router lists. The lists' priorities 213 // from root to leaf are from low to high. 214 var p = s.serveTree[domain] 215 for i, part := range array { 216 // Ignore empty URI part, like: /user//index 217 if part == "" { 218 continue 219 } 220 // Check if it's a fuzzy node. 221 if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) { 222 part = "*fuzz" 223 // If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map. 224 // All the sub router items from this fuzzy node will also be added to its "*list" item. 225 if v, ok := p.(map[string]interface{})["*list"]; !ok { 226 newListForFuzzy := glist.New() 227 p.(map[string]interface{})["*list"] = newListForFuzzy 228 lists = append(lists, newListForFuzzy) 229 } else { 230 lists = append(lists, v.(*glist.List)) 231 } 232 } 233 // Make a new bucket for the current node. 234 if _, ok := p.(map[string]interface{})[part]; !ok { 235 p.(map[string]interface{})[part] = make(map[string]interface{}) 236 } 237 // Loop to next bucket. 238 p = p.(map[string]interface{})[part] 239 // The leaf is a hash map and must have an item named "*list", which contains the router item. 240 // The leaf can be furthermore extended by adding more ket-value pairs into its map. 241 // Note that the `v != "*fuzz"` comparison is required as the list might be added in the former 242 // fuzzy checks. 243 if i == len(array)-1 && part != "*fuzz" { 244 if v, ok := p.(map[string]interface{})["*list"]; !ok { 245 leafList := glist.New() 246 p.(map[string]interface{})["*list"] = leafList 247 lists = append(lists, leafList) 248 } else { 249 lists = append(lists, v.(*glist.List)) 250 } 251 } 252 } 253 // It iterates the list array of `lists`, compares priorities and inserts the new router item in 254 // the proper position of each list. The priority of the list is ordered from high to low. 255 var item *HandlerItem 256 for _, l := range lists { 257 pushed := false 258 for e := l.Front(); e != nil; e = e.Next() { 259 item = e.Value.(*HandlerItem) 260 // Checks the priority whether inserting the route item before current item, 261 // which means it has higher priority. 262 if s.compareRouterPriority(handler, item) { 263 l.InsertBefore(e, handler) 264 pushed = true 265 goto end 266 } 267 } 268 end: 269 // Just push back in default. 270 if !pushed { 271 l.PushBack(handler) 272 } 273 } 274 // Initialize the route map item. 275 if _, ok := s.routesMap[routerKey]; !ok { 276 s.routesMap[routerKey] = make([]*HandlerItem, 0) 277 } 278 279 // Append the route. 280 s.routesMap[routerKey] = append(s.routesMap[routerKey], handler) 281 } 282 283 func (s *Server) isValidMethod(method string) bool { 284 if gstr.Equal(method, defaultMethod) { 285 return true 286 } 287 _, ok := methodsMap[strings.ToUpper(method)] 288 return ok 289 } 290 291 // compareRouterPriority compares the priority between `newItem` and `oldItem`. It returns true 292 // if `newItem`'s priority is higher than `oldItem`, else it returns false. The higher priority 293 // item will be inserted into the router list before the other one. 294 // 295 // Comparison rules: 296 // 1. The middleware has the most high priority. 297 // 2. URI: The deeper, the higher (simply check the count of char '/' in the URI). 298 // 3. Route type: {xxx} > :xxx > *xxx. 299 func (s *Server) compareRouterPriority(newItem *HandlerItem, oldItem *HandlerItem) bool { 300 // If they're all types of middleware, the priority is according to their registered sequence. 301 if newItem.Type == HandlerTypeMiddleware && oldItem.Type == HandlerTypeMiddleware { 302 return false 303 } 304 // The middleware has the most high priority. 305 if newItem.Type == HandlerTypeMiddleware && oldItem.Type != HandlerTypeMiddleware { 306 return true 307 } 308 // URI: The deeper, the higher (simply check the count of char '/' in the URI). 309 if newItem.Router.Priority > oldItem.Router.Priority { 310 return true 311 } 312 if newItem.Router.Priority < oldItem.Router.Priority { 313 return false 314 } 315 316 // Compare the length of their URI, 317 // but the fuzzy and named parts of the URI are not calculated to the result. 318 319 // Example: 320 // /admin-goods-{page} > /admin-{page} 321 // /{hash}.{type} > /{hash} 322 var uriNew, uriOld string 323 uriNew, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", newItem.Router.Uri) 324 uriOld, _ = gregex.ReplaceString(`\{[^/]+?\}`, "", oldItem.Router.Uri) 325 uriNew, _ = gregex.ReplaceString(`:[^/]+?`, "", uriNew) 326 uriOld, _ = gregex.ReplaceString(`:[^/]+?`, "", uriOld) 327 uriNew, _ = gregex.ReplaceString(`\*[^/]*`, "", uriNew) // Replace "/*" and "/*any". 328 uriOld, _ = gregex.ReplaceString(`\*[^/]*`, "", uriOld) // Replace "/*" and "/*any". 329 if len(uriNew) > len(uriOld) { 330 return true 331 } 332 if len(uriNew) < len(uriOld) { 333 return false 334 } 335 336 // Route type checks: {xxx} > :xxx > *xxx. 337 // Example: 338 // /name/act > /{name}/:act 339 var ( 340 fuzzyCountFieldNew int 341 fuzzyCountFieldOld int 342 fuzzyCountNameNew int 343 fuzzyCountNameOld int 344 fuzzyCountAnyNew int 345 fuzzyCountAnyOld int 346 fuzzyCountTotalNew int 347 fuzzyCountTotalOld int 348 ) 349 for _, v := range newItem.Router.Uri { 350 switch v { 351 case '{': 352 fuzzyCountFieldNew++ 353 case ':': 354 fuzzyCountNameNew++ 355 case '*': 356 fuzzyCountAnyNew++ 357 } 358 } 359 for _, v := range oldItem.Router.Uri { 360 switch v { 361 case '{': 362 fuzzyCountFieldOld++ 363 case ':': 364 fuzzyCountNameOld++ 365 case '*': 366 fuzzyCountAnyOld++ 367 } 368 } 369 fuzzyCountTotalNew = fuzzyCountFieldNew + fuzzyCountNameNew + fuzzyCountAnyNew 370 fuzzyCountTotalOld = fuzzyCountFieldOld + fuzzyCountNameOld + fuzzyCountAnyOld 371 if fuzzyCountTotalNew < fuzzyCountTotalOld { 372 return true 373 } 374 if fuzzyCountTotalNew > fuzzyCountTotalOld { 375 return false 376 } 377 378 // If the counts of their fuzzy rules are equal. 379 380 // Eg: /name/{act} > /name/:act 381 if fuzzyCountFieldNew > fuzzyCountFieldOld { 382 return true 383 } 384 if fuzzyCountFieldNew < fuzzyCountFieldOld { 385 return false 386 } 387 // Eg: /name/:act > /name/*act 388 if fuzzyCountNameNew > fuzzyCountNameOld { 389 return true 390 } 391 if fuzzyCountNameNew < fuzzyCountNameOld { 392 return false 393 } 394 395 // It then compares the accuracy of their http method, 396 // the more accurate the more priority. 397 if newItem.Router.Method != defaultMethod { 398 return true 399 } 400 if oldItem.Router.Method != defaultMethod { 401 return true 402 } 403 404 // If they have different router type, 405 // the new router item has more priority than the other one. 406 if newItem.Type == HandlerTypeHandler || newItem.Type == HandlerTypeObject { 407 return true 408 } 409 410 // Other situations, like HOOK items, 411 // the old router item has more priority than the other one. 412 return false 413 } 414 415 // patternToRegular converts route rule to according to regular expression. 416 func (s *Server) patternToRegular(rule string) (regular string, names []string) { 417 if len(rule) < 2 { 418 return rule, nil 419 } 420 regular = "^" 421 var array = strings.Split(rule[1:], "/") 422 for _, v := range array { 423 if len(v) == 0 { 424 continue 425 } 426 switch v[0] { 427 case ':': 428 if len(v) > 1 { 429 regular += `/([^/]+)` 430 names = append(names, v[1:]) 431 } else { 432 regular += `/[^/]+` 433 } 434 case '*': 435 if len(v) > 1 { 436 regular += `/{0,1}(.*)` 437 names = append(names, v[1:]) 438 } else { 439 regular += `/{0,1}.*` 440 } 441 default: 442 // Special chars replacement. 443 v = gstr.ReplaceByMap(v, map[string]string{ 444 `.`: `\.`, 445 `+`: `\+`, 446 `*`: `.*`, 447 }) 448 s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string { 449 names = append(names, s[1:len(s)-1]) 450 return `([^/]+)` 451 }) 452 if strings.EqualFold(s, v) { 453 regular += "/" + v 454 } else { 455 regular += "/" + s 456 } 457 } 458 } 459 regular += `$` 460 return 461 }