router.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
  2. // 🤖 GitHub Repository: https://github.com/gofiber/fiber
  3. // 📌 API Documentation: https://docs.gofiber.io
  4. package fiber
  5. import (
  6. "fmt"
  7. "slices"
  8. "sync/atomic"
  9. "github.com/gofiber/utils/v2"
  10. utilsstrings "github.com/gofiber/utils/v2/strings"
  11. "github.com/valyala/fasthttp"
  12. )
  13. // Router defines all router handle interface, including app and group router.
  14. type Router interface {
  15. Use(args ...any) Router
  16. Get(path string, handler any, handlers ...any) Router
  17. Head(path string, handler any, handlers ...any) Router
  18. Post(path string, handler any, handlers ...any) Router
  19. Put(path string, handler any, handlers ...any) Router
  20. Delete(path string, handler any, handlers ...any) Router
  21. Connect(path string, handler any, handlers ...any) Router
  22. Options(path string, handler any, handlers ...any) Router
  23. Trace(path string, handler any, handlers ...any) Router
  24. Patch(path string, handler any, handlers ...any) Router
  25. Add(methods []string, path string, handler any, handlers ...any) Router
  26. All(path string, handler any, handlers ...any) Router
  27. Group(prefix string, handlers ...any) Router
  28. RouteChain(path string) Register
  29. Route(prefix string, fn func(router Router), name ...string) Router
  30. Name(name string) Router
  31. }
  32. // Route is a struct that holds all metadata for each registered handler.
  33. type Route struct {
  34. // ### important: always keep in sync with the copy method "app.copyRoute" and all creations of Route struct ###
  35. group *Group // Group instance. used for routes in groups
  36. path string // Prettified path
  37. // Public fields
  38. Method string `json:"method"` // HTTP method
  39. Name string `json:"name"` // Route's name
  40. //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine
  41. Path string `json:"path"` // Original registered route path
  42. Params []string `json:"params"` // Case-sensitive param keys
  43. Handlers []Handler `json:"-"` // Ctx handlers
  44. routeParser routeParser // Parameter parser
  45. // Data for routing
  46. use bool // USE matches path prefixes
  47. mount bool // Indicated a mounted app on a specific route
  48. star bool // Path equals '*'
  49. root bool // Path equals '/'
  50. autoHead bool // Automatically generated HEAD route
  51. }
  52. func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {
  53. // root detectionPath check
  54. if r.root && len(detectionPath) == 1 && detectionPath[0] == '/' {
  55. return true
  56. }
  57. // '*' wildcard matches any detectionPath
  58. if r.star {
  59. if len(path) > 1 {
  60. params[0] = path[1:]
  61. } else {
  62. params[0] = ""
  63. }
  64. return true
  65. }
  66. // Does this route have parameters?
  67. if len(r.Params) > 0 {
  68. // Match params using precomputed routeParser
  69. if r.routeParser.getMatch(detectionPath, path, params, r.use) {
  70. return true
  71. }
  72. }
  73. // Middleware route?
  74. if r.use {
  75. // Single slash or prefix match
  76. plen := len(r.path)
  77. if r.root {
  78. // If r.root is '/', it matches everything starting at '/'
  79. if detectionPath != "" && detectionPath[0] == '/' {
  80. return true
  81. }
  82. } else if len(detectionPath) >= plen && detectionPath[:plen] == r.path {
  83. if hasPartialMatchBoundary(detectionPath, plen) {
  84. return true
  85. }
  86. }
  87. } else if len(r.path) == len(detectionPath) && detectionPath == r.path {
  88. // Check exact match
  89. return true
  90. }
  91. // No match
  92. return false
  93. }
  94. func (app *App) next(c *DefaultCtx) (bool, error) {
  95. methodInt := c.methodInt
  96. treeHash := c.treePathHash
  97. // Get stack length
  98. tree, ok := app.treeStack[methodInt][treeHash]
  99. if !ok {
  100. tree = app.treeStack[methodInt][0]
  101. }
  102. lenr := len(tree) - 1
  103. indexRoute := c.indexRoute
  104. // Loop over the route stack starting from previous index
  105. for indexRoute < lenr {
  106. // Increment route index
  107. indexRoute++
  108. // Get *Route
  109. route := tree[indexRoute]
  110. if route.mount {
  111. continue
  112. }
  113. // Check if it matches the request path
  114. if !route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values) {
  115. continue
  116. }
  117. if c.skipNonUseRoutes && !route.use {
  118. continue
  119. }
  120. // Pass route reference and param values
  121. c.route = route
  122. // Non use handler matched
  123. if !route.use {
  124. c.matched = true
  125. }
  126. // Execute first handler of route
  127. if len(route.Handlers) > 0 {
  128. c.indexHandler = 0
  129. c.indexRoute = indexRoute
  130. return true, route.Handlers[0](c)
  131. }
  132. return true, nil // Stop scanning the stack
  133. }
  134. // If c.Next() does not match, return 404
  135. // If no match, scan stack again if other methods match the request
  136. // Moved from app.handler because middleware may break the route chain
  137. if c.matched {
  138. return false, ErrNotFound
  139. }
  140. exists := false
  141. methods := app.config.RequestMethods
  142. for i := range methods {
  143. // Skip original method
  144. if methodInt == i {
  145. continue
  146. }
  147. // Reset stack index
  148. indexRoute := -1
  149. tree, ok := app.treeStack[i][treeHash]
  150. if !ok {
  151. tree = app.treeStack[i][0]
  152. }
  153. // Get stack length
  154. lenr := len(tree) - 1
  155. // Loop over the route stack starting from previous index
  156. for indexRoute < lenr {
  157. // Increment route index
  158. indexRoute++
  159. // Get *Route
  160. route := tree[indexRoute]
  161. // Skip use routes
  162. if route.use {
  163. continue
  164. }
  165. // Check if it matches the request path
  166. // No match, next route
  167. if route.match(utils.UnsafeString(c.detectionPath), utils.UnsafeString(c.path), &c.values) {
  168. // We matched
  169. exists = true
  170. // Add method to Allow header
  171. c.Append(HeaderAllow, methods[i])
  172. // Break stack loop
  173. break
  174. }
  175. }
  176. c.indexRoute = indexRoute
  177. }
  178. if exists {
  179. return false, ErrMethodNotAllowed
  180. }
  181. return false, ErrNotFound
  182. }
  183. func (app *App) nextCustom(c CustomCtx) (bool, error) {
  184. methodInt := c.getMethodInt()
  185. treeHash := c.getTreePathHash()
  186. // Get stack length
  187. tree, ok := app.treeStack[methodInt][treeHash]
  188. if !ok {
  189. tree = app.treeStack[methodInt][0]
  190. }
  191. lenr := len(tree) - 1
  192. indexRoute := c.getIndexRoute()
  193. // Loop over the route stack starting from previous index
  194. for indexRoute < lenr {
  195. // Increment route index
  196. indexRoute++
  197. // Get *Route
  198. route := tree[indexRoute]
  199. if route.mount {
  200. continue
  201. }
  202. // Check if it matches the request path
  203. if !route.match(c.getDetectionPath(), c.Path(), c.getValues()) {
  204. continue
  205. }
  206. if c.getSkipNonUseRoutes() && !route.use {
  207. continue
  208. }
  209. // Pass route reference and param values
  210. c.setRoute(route)
  211. // Non use handler matched
  212. if !route.use {
  213. c.setMatched(true)
  214. }
  215. // Execute first handler of route
  216. if len(route.Handlers) > 0 {
  217. c.setIndexHandler(0)
  218. c.setIndexRoute(indexRoute)
  219. return true, route.Handlers[0](c)
  220. }
  221. return true, nil // Stop scanning the stack
  222. }
  223. // If c.Next() does not match, return 404
  224. // If no match, scan stack again if other methods match the request
  225. // Moved from app.handler because middleware may break the route chain
  226. if c.getMatched() {
  227. return false, ErrNotFound
  228. }
  229. exists := false
  230. methods := app.config.RequestMethods
  231. for i := range methods {
  232. // Skip original method
  233. if methodInt == i {
  234. continue
  235. }
  236. // Reset stack index
  237. indexRoute := -1
  238. tree, ok := app.treeStack[i][treeHash]
  239. if !ok {
  240. tree = app.treeStack[i][0]
  241. }
  242. // Get stack length
  243. lenr := len(tree) - 1
  244. // Loop over the route stack starting from previous index
  245. for indexRoute < lenr {
  246. // Increment route index
  247. indexRoute++
  248. // Get *Route
  249. route := tree[indexRoute]
  250. // Skip use routes
  251. if route.use {
  252. continue
  253. }
  254. // Check if it matches the request path
  255. // No match, next route
  256. if route.match(c.getDetectionPath(), c.Path(), c.getValues()) {
  257. // We matched
  258. exists = true
  259. // Add method to Allow header
  260. c.Append(HeaderAllow, methods[i])
  261. // Break stack loop
  262. break
  263. }
  264. }
  265. c.setIndexRoute(indexRoute)
  266. }
  267. if exists {
  268. return false, ErrMethodNotAllowed
  269. }
  270. return false, ErrNotFound
  271. }
  272. func (app *App) requestHandler(rctx *fasthttp.RequestCtx) {
  273. // Acquire context from the pool
  274. ctx := app.AcquireCtx(rctx)
  275. defer app.ReleaseCtx(ctx)
  276. var err error
  277. // Attempt to match a route and execute the chain
  278. if d, isDefault := ctx.(*DefaultCtx); isDefault {
  279. // Check if the HTTP method is valid
  280. if d.methodInt == -1 {
  281. _ = d.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
  282. return
  283. }
  284. // Optional: check flash messages (hot path, see hasFlashCookie).
  285. if hasFlashCookie(&d.Request().Header) {
  286. d.Redirect().parseAndClearFlashMessages()
  287. }
  288. _, err = app.next(d)
  289. } else {
  290. // Check if the HTTP method is valid
  291. if ctx.getMethodInt() == -1 {
  292. _ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
  293. return
  294. }
  295. // Optional: check flash messages (hot path, see hasFlashCookie).
  296. if hasFlashCookie(&ctx.Request().Header) {
  297. ctx.Redirect().parseAndClearFlashMessages()
  298. }
  299. _, err = app.nextCustom(ctx)
  300. }
  301. if err != nil {
  302. if catch := ctx.App().ErrorHandler(ctx, err); catch != nil {
  303. _ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil
  304. }
  305. return
  306. }
  307. }
  308. func (app *App) addPrefixToRoute(prefix string, route *Route) *Route {
  309. prefixedPath := getGroupPath(prefix, route.Path)
  310. prettyPath := prefixedPath
  311. // Case-sensitive routing, all to lowercase
  312. if !app.config.CaseSensitive {
  313. prettyPath = utilsstrings.ToLower(prettyPath)
  314. }
  315. // Strict routing, remove trailing slashes
  316. if !app.config.StrictRouting && len(prettyPath) > 1 {
  317. prettyPath = utils.TrimRight(prettyPath, '/')
  318. }
  319. route.Path = prefixedPath
  320. route.path = RemoveEscapeChar(prettyPath)
  321. route.routeParser = parseRoute(prettyPath, app.customConstraints...)
  322. route.root = false
  323. route.star = false
  324. return route
  325. }
  326. func (*App) copyRoute(route *Route) *Route {
  327. return &Route{
  328. // Router booleans
  329. use: route.use,
  330. mount: route.mount,
  331. star: route.star,
  332. root: route.root,
  333. autoHead: route.autoHead,
  334. // Path data
  335. path: route.path,
  336. routeParser: route.routeParser,
  337. // Public data
  338. Path: route.Path,
  339. Params: route.Params,
  340. Name: route.Name,
  341. Method: route.Method,
  342. Handlers: route.Handlers,
  343. }
  344. }
  345. func (app *App) normalizePath(path string) string {
  346. if path == "" {
  347. path = "/"
  348. }
  349. if path[0] != '/' {
  350. path = "/" + path
  351. }
  352. if !app.config.CaseSensitive {
  353. path = utilsstrings.ToLower(path)
  354. }
  355. if !app.config.StrictRouting && len(path) > 1 {
  356. path = utils.TrimRight(path, '/')
  357. }
  358. return RemoveEscapeChar(path)
  359. }
  360. // RemoveRoute is used to remove a route from the stack by path.
  361. // If no methods are specified, it will remove the route for all methods defined in the app.
  362. // You should call RebuildTree after using this to ensure consistency of the tree.
  363. func (app *App) RemoveRoute(path string, methods ...string) {
  364. // Normalize same as register uses
  365. norm := app.normalizePath(path)
  366. pathMatchFunc := func(r *Route) bool {
  367. return r.path == norm // compare private normalized path
  368. }
  369. app.deleteRoute(methods, pathMatchFunc)
  370. }
  371. // RemoveRouteByName is used to remove a route from the stack by name.
  372. // If no methods are specified, it will remove the route for all methods defined in the app.
  373. // You should call RebuildTree after using this to ensure consistency of the tree.
  374. func (app *App) RemoveRouteByName(name string, methods ...string) {
  375. matchFunc := func(r *Route) bool { return r.Name == name }
  376. app.deleteRoute(methods, matchFunc)
  377. }
  378. // RemoveRouteFunc is used to remove a route from the stack by a custom match function.
  379. // If no methods are specified, it will remove the route for all methods defined in the app.
  380. // You should call RebuildTree after using this to ensure consistency of the tree.
  381. // Note: The route.Path is original path, not the normalized path.
  382. func (app *App) RemoveRouteFunc(matchFunc func(r *Route) bool, methods ...string) {
  383. app.deleteRoute(methods, matchFunc)
  384. }
  385. func (app *App) deleteRoute(methods []string, matchFunc func(r *Route) bool) {
  386. if len(methods) == 0 {
  387. methods = app.config.RequestMethods
  388. }
  389. app.mutex.Lock()
  390. defer app.mutex.Unlock()
  391. removedUseRoutes := make(map[string]struct{})
  392. for _, method := range methods {
  393. // Uppercase HTTP methods
  394. method = utilsstrings.ToUpper(method)
  395. // Get unique HTTP method identifier
  396. m := app.methodInt(method)
  397. if m == -1 {
  398. continue // Skip invalid HTTP methods
  399. }
  400. for i := len(app.stack[m]) - 1; i >= 0; i-- {
  401. route := app.stack[m][i]
  402. if !matchFunc(route) {
  403. continue // Skip if route does not match
  404. }
  405. app.stack[m] = append(app.stack[m][:i], app.stack[m][i+1:]...)
  406. app.routesRefreshed = true
  407. // Decrement global handler count. In middleware routes, only decrement once
  408. if _, ok := removedUseRoutes[route.path]; (route.use && slices.Equal(methods, app.config.RequestMethods) && !ok) || !route.use {
  409. if route.use {
  410. removedUseRoutes[route.path] = struct{}{}
  411. }
  412. atomic.AddUint32(&app.handlersCount, ^uint32(len(route.Handlers)-1)) //nolint:gosec // G115 - handler count is always small
  413. }
  414. if method == MethodGet && !route.use && !route.mount {
  415. app.pruneAutoHeadRouteLocked(route.path)
  416. }
  417. }
  418. }
  419. }
  420. // pruneAutoHeadRouteLocked removes an automatically generated HEAD route so a
  421. // later explicit registration can take its place without duplicating handler
  422. // chains. The caller must already hold app.mutex.
  423. func (app *App) pruneAutoHeadRouteLocked(path string) {
  424. headIndex := app.methodInt(MethodHead)
  425. if headIndex == -1 {
  426. return
  427. }
  428. norm := app.normalizePath(path)
  429. headStack := app.stack[headIndex]
  430. for i := len(headStack) - 1; i >= 0; i-- {
  431. headRoute := headStack[i]
  432. if headRoute.path != norm || headRoute.mount || headRoute.use || !headRoute.autoHead {
  433. continue
  434. }
  435. app.stack[headIndex] = append(headStack[:i], headStack[i+1:]...)
  436. app.routesRefreshed = true
  437. atomic.AddUint32(&app.handlersCount, ^uint32(len(headRoute.Handlers)-1)) //nolint:gosec // G115 - handler count is always small
  438. return
  439. }
  440. }
  441. func (app *App) register(methods []string, pathRaw string, group *Group, handlers ...Handler) {
  442. // A regular route requires at least one ctx handler
  443. if len(handlers) == 0 && group == nil {
  444. panic(fmt.Sprintf("missing handler/middleware in route: %s\n", pathRaw))
  445. }
  446. // No nil handlers allowed
  447. for _, h := range handlers {
  448. if h == nil {
  449. panic(fmt.Sprintf("nil handler in route: %s\n", pathRaw))
  450. }
  451. }
  452. // Precompute path normalization ONCE
  453. if pathRaw == "" {
  454. pathRaw = "/"
  455. }
  456. if pathRaw[0] != '/' {
  457. pathRaw = "/" + pathRaw
  458. }
  459. pathPretty := pathRaw
  460. if !app.config.CaseSensitive {
  461. pathPretty = utilsstrings.ToLower(pathPretty)
  462. }
  463. if !app.config.StrictRouting && len(pathPretty) > 1 {
  464. pathPretty = utils.TrimRight(pathPretty, '/')
  465. }
  466. pathClean := RemoveEscapeChar(pathPretty)
  467. parsedRaw := parseRoute(pathRaw, app.customConstraints...)
  468. parsedPretty := parseRoute(pathPretty, app.customConstraints...)
  469. isMount := group != nil && group.app != app
  470. for _, method := range methods {
  471. method = utilsstrings.ToUpper(method)
  472. if method != methodUse && app.methodInt(method) == -1 {
  473. panic(fmt.Sprintf("add: invalid http method %s\n", method))
  474. }
  475. isUse := method == methodUse
  476. isStar := pathClean == "/*"
  477. isRoot := pathClean == "/"
  478. route := Route{
  479. use: isUse,
  480. mount: isMount,
  481. star: isStar,
  482. root: isRoot,
  483. path: pathClean,
  484. routeParser: parsedPretty,
  485. Params: parsedRaw.params,
  486. group: group,
  487. Path: pathRaw,
  488. Method: method,
  489. Handlers: handlers,
  490. }
  491. // Increment global handler count
  492. atomic.AddUint32(&app.handlersCount, uint32(len(handlers))) //nolint:gosec // G115 - handler count is always small
  493. // Middleware route matches all HTTP methods
  494. if isUse {
  495. // Add route to all HTTP methods stack
  496. for _, m := range app.config.RequestMethods {
  497. // Create a route copy to avoid duplicates during compression
  498. r := route
  499. app.addRoute(m, &r)
  500. }
  501. } else {
  502. // Add route to stack
  503. app.addRoute(method, &route)
  504. }
  505. }
  506. }
  507. func (app *App) addRoute(method string, route *Route) {
  508. app.mutex.Lock()
  509. defer app.mutex.Unlock()
  510. // Get unique HTTP method identifier
  511. m := app.methodInt(method)
  512. if method == MethodHead && !route.mount && !route.use {
  513. app.pruneAutoHeadRouteLocked(route.path)
  514. }
  515. // prevent identically route registration
  516. l := len(app.stack[m])
  517. if l > 0 && app.stack[m][l-1].Path == route.Path && route.use == app.stack[m][l-1].use && !route.mount && !app.stack[m][l-1].mount {
  518. preRoute := app.stack[m][l-1]
  519. preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
  520. } else {
  521. route.Method = method
  522. // Add route to the stack
  523. app.stack[m] = append(app.stack[m], route)
  524. app.routesRefreshed = true
  525. }
  526. // Execute onRoute hooks & change latestRoute if not adding mounted route
  527. if !route.mount {
  528. app.latestRoute = route
  529. if err := app.hooks.executeOnRouteHooks(route); err != nil {
  530. panic(err)
  531. }
  532. }
  533. }
  534. func (app *App) ensureAutoHeadRoutes() {
  535. app.mutex.Lock()
  536. defer app.mutex.Unlock()
  537. app.ensureAutoHeadRoutesLocked()
  538. }
  539. func (app *App) ensureAutoHeadRoutesLocked() {
  540. if app.config.DisableHeadAutoRegister {
  541. return
  542. }
  543. headIndex := app.methodInt(MethodHead)
  544. getIndex := app.methodInt(MethodGet)
  545. if headIndex == -1 || getIndex == -1 {
  546. return
  547. }
  548. headStack := app.stack[headIndex]
  549. existing := make(map[string]struct{}, len(headStack))
  550. for _, route := range headStack {
  551. if route.mount || route.use {
  552. continue
  553. }
  554. existing[route.path] = struct{}{}
  555. }
  556. if len(app.stack[getIndex]) == 0 {
  557. return
  558. }
  559. var added bool
  560. for _, route := range app.stack[getIndex] {
  561. if route.mount || route.use {
  562. continue
  563. }
  564. if _, ok := existing[route.path]; ok {
  565. continue
  566. }
  567. headRoute := app.copyRoute(route)
  568. headRoute.group = route.group
  569. headRoute.Method = MethodHead
  570. headRoute.autoHead = true
  571. // Fasthttp automatically omits response bodies when transmitting
  572. // HEAD responses, so the copied GET handler stack can execute
  573. // unchanged while still producing an empty body on the wire.
  574. headStack = append(headStack, headRoute)
  575. existing[route.path] = struct{}{}
  576. app.routesRefreshed = true
  577. added = true
  578. atomic.AddUint32(&app.handlersCount, uint32(len(headRoute.Handlers))) //nolint:gosec // G115 - handler count is always small
  579. app.latestRoute = headRoute
  580. if err := app.hooks.executeOnRouteHooks(headRoute); err != nil {
  581. panic(err)
  582. }
  583. }
  584. if added {
  585. app.stack[headIndex] = headStack
  586. }
  587. }
  588. // RebuildTree rebuilds the prefix tree from the previously registered routes.
  589. // This method is useful when you want to register routes dynamically after the app has started.
  590. // It is not recommended to use this method on production environments because rebuilding
  591. // the tree is performance-intensive and not thread-safe in runtime. Since building the tree
  592. // is only done in the startupProcess of the app, this method does not make sure that the
  593. // routeTree is being safely changed, as it would add a great deal of overhead in the request.
  594. // Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in:
  595. // https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283
  596. func (app *App) RebuildTree() *App {
  597. app.mutex.Lock()
  598. defer app.mutex.Unlock()
  599. return app.buildTree()
  600. }
  601. // buildTree build the prefix tree from the previously registered routes
  602. func (app *App) buildTree() *App {
  603. // If routes haven't been refreshed, nothing to do
  604. if !app.routesRefreshed {
  605. return app
  606. }
  607. // 1) First loop: determine all possible 3-char prefixes ("treePaths") for each method
  608. for method := range app.config.RequestMethods {
  609. routes := app.stack[method]
  610. treePaths := make([]int, len(routes))
  611. globalCount := 0
  612. prefixCounts := make(map[int]int, len(routes))
  613. for i, route := range routes {
  614. if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
  615. treePaths[i] = int(route.routeParser.segs[0].Const[0])<<16 |
  616. int(route.routeParser.segs[0].Const[1])<<8 |
  617. int(route.routeParser.segs[0].Const[2])
  618. }
  619. if treePaths[i] == 0 {
  620. globalCount++
  621. continue
  622. }
  623. prefixCounts[treePaths[i]]++
  624. }
  625. prevBuckets := app.treeStack[method]
  626. tsMap := make(map[int][]*Route, len(prefixCounts)+1)
  627. tsMap[0] = reuseRouteBucket(prevBuckets, 0, globalCount)
  628. for treePath, count := range prefixCounts {
  629. tsMap[treePath] = reuseRouteBucket(prevBuckets, treePath, count+globalCount)
  630. }
  631. for i, route := range routes {
  632. treePath := treePaths[i]
  633. if treePath == 0 {
  634. for bucket := range tsMap {
  635. tsMap[bucket] = append(tsMap[bucket], route)
  636. }
  637. continue
  638. }
  639. tsMap[treePath] = append(tsMap[treePath], route)
  640. }
  641. app.treeStack[method] = tsMap
  642. }
  643. // reset the flag and return
  644. app.routesRefreshed = false
  645. return app
  646. }
  647. func reuseRouteBucket(prev map[int][]*Route, key, capHint int) []*Route {
  648. if bucket, ok := prev[key]; ok && cap(bucket) >= capHint {
  649. return bucket[:0]
  650. }
  651. return make([]*Route, 0, capHint)
  652. }