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 }