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 }