router.go 24 KB

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