router.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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. "sort"
  8. "strconv"
  9. "strings"
  10. "sync/atomic"
  11. "time"
  12. "github.com/gofiber/fiber/v2/utils"
  13. "github.com/valyala/fasthttp"
  14. )
  15. // Router defines all router handle interface, including app and group router.
  16. type Router interface {
  17. Use(args ...interface{}) Router
  18. Get(path string, handlers ...Handler) Router
  19. Head(path string, handlers ...Handler) Router
  20. Post(path string, handlers ...Handler) Router
  21. Put(path string, handlers ...Handler) Router
  22. Delete(path string, handlers ...Handler) Router
  23. Connect(path string, handlers ...Handler) Router
  24. Options(path string, handlers ...Handler) Router
  25. Trace(path string, handlers ...Handler) Router
  26. Patch(path string, handlers ...Handler) Router
  27. Add(method, path string, handlers ...Handler) Router
  28. Static(prefix, root string, config ...Static) Router
  29. All(path string, handlers ...Handler) Router
  30. Group(prefix string, handlers ...Handler) Router
  31. Route(prefix string, fn func(router Router), name ...string) Router
  32. Mount(prefix string, fiber *App) Router
  33. Name(name string) Router
  34. }
  35. // Route is a struct that holds all metadata for each registered handler.
  36. type Route struct {
  37. // always keep in sync with the copy method "app.copyRoute"
  38. // Data for routing
  39. pos uint32 // Position in stack -> important for the sort of the matched routes
  40. use bool // USE matches path prefixes
  41. mount bool // Indicated a mounted app on a specific route
  42. star bool // Path equals '*'
  43. root bool // Path equals '/'
  44. path string // Prettified path
  45. routeParser routeParser // Parameter parser
  46. group *Group // Group instance. used for routes in groups
  47. // Public fields
  48. Method string `json:"method"` // HTTP method
  49. Name string `json:"name"` // Route's name
  50. //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine
  51. Path string `json:"path"` // Original registered route path
  52. Params []string `json:"params"` // Case sensitive param keys
  53. Handlers []Handler `json:"-"` // Ctx handlers
  54. }
  55. func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {
  56. // root detectionPath check
  57. if r.root && detectionPath == "/" {
  58. return true
  59. // '*' wildcard matches any detectionPath
  60. } else if r.star {
  61. if len(path) > 1 {
  62. params[0] = path[1:]
  63. } else {
  64. params[0] = ""
  65. }
  66. return true
  67. }
  68. // Does this route have parameters
  69. if len(r.Params) > 0 {
  70. // Match params
  71. if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match {
  72. // Get params from the path detectionPath
  73. return match
  74. }
  75. }
  76. // Is this route a Middleware?
  77. if r.use {
  78. // Single slash will match or detectionPath prefix
  79. if r.root || strings.HasPrefix(detectionPath, r.path) {
  80. return true
  81. }
  82. // Check for a simple detectionPath match
  83. } else if len(r.path) == len(detectionPath) && r.path == detectionPath {
  84. return true
  85. }
  86. // No match
  87. return false
  88. }
  89. func (app *App) next(c *Ctx) (bool, error) {
  90. // Get stack length
  91. tree, ok := app.treeStack[c.methodINT][c.treePath]
  92. if !ok {
  93. tree = app.treeStack[c.methodINT][""]
  94. }
  95. lenTree := len(tree) - 1
  96. // Loop over the route stack starting from previous index
  97. for c.indexRoute < lenTree {
  98. // Increment route index
  99. c.indexRoute++
  100. // Get *Route
  101. route := tree[c.indexRoute]
  102. var match bool
  103. var err error
  104. // skip for mounted apps
  105. if route.mount {
  106. continue
  107. }
  108. // Check if it matches the request path
  109. match = route.match(c.detectionPath, c.path, &c.values)
  110. if !match {
  111. // No match, next route
  112. continue
  113. }
  114. // Pass route reference and param values
  115. c.route = route
  116. // Non use handler matched
  117. if !c.matched && !route.use {
  118. c.matched = true
  119. }
  120. // Execute first handler of route
  121. c.indexHandler = 0
  122. if len(route.Handlers) > 0 {
  123. err = route.Handlers[0](c)
  124. }
  125. return match, err // Stop scanning the stack
  126. }
  127. // If c.Next() does not match, return 404
  128. err := NewError(StatusNotFound, "Cannot "+c.method+" "+c.pathOriginal)
  129. if !c.matched && app.methodExist(c) {
  130. // If no match, scan stack again if other methods match the request
  131. // Moved from app.handler because middleware may break the route chain
  132. err = ErrMethodNotAllowed
  133. }
  134. return false, err
  135. }
  136. func (app *App) handler(rctx *fasthttp.RequestCtx) { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476
  137. // Acquire Ctx with fasthttp request from pool
  138. c := app.AcquireCtx(rctx)
  139. defer app.ReleaseCtx(c)
  140. // handle invalid http method directly
  141. if c.methodINT == -1 {
  142. _ = c.Status(StatusBadRequest).SendString("Invalid http method") //nolint:errcheck // It is fine to ignore the error here
  143. return
  144. }
  145. // Find match in stack
  146. match, err := app.next(c)
  147. if err != nil {
  148. if catch := c.app.ErrorHandler(c, err); catch != nil {
  149. _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here
  150. }
  151. // TODO: Do we need to return here?
  152. }
  153. // Generate ETag if enabled
  154. if match && app.config.ETag {
  155. setETag(c, false)
  156. }
  157. }
  158. func (app *App) addPrefixToRoute(prefix string, route *Route) *Route {
  159. prefixedPath := getGroupPath(prefix, route.Path)
  160. prettyPath := prefixedPath
  161. // Case sensitive routing, all to lowercase
  162. if !app.config.CaseSensitive {
  163. prettyPath = utils.ToLower(prettyPath)
  164. }
  165. // Strict routing, remove trailing slashes
  166. if !app.config.StrictRouting && len(prettyPath) > 1 {
  167. prettyPath = utils.TrimRight(prettyPath, '/')
  168. }
  169. route.Path = prefixedPath
  170. route.path = RemoveEscapeChar(prettyPath)
  171. route.routeParser = parseRoute(prettyPath)
  172. route.root = false
  173. route.star = false
  174. return route
  175. }
  176. func (*App) copyRoute(route *Route) *Route {
  177. return &Route{
  178. // Router booleans
  179. use: route.use,
  180. mount: route.mount,
  181. star: route.star,
  182. root: route.root,
  183. // Path data
  184. path: route.path,
  185. routeParser: route.routeParser,
  186. Params: route.Params,
  187. // misc
  188. pos: route.pos,
  189. // Public data
  190. Path: route.Path,
  191. Method: route.Method,
  192. Handlers: route.Handlers,
  193. }
  194. }
  195. func (app *App) register(method, pathRaw string, group *Group, handlers ...Handler) Router {
  196. // Uppercase HTTP methods
  197. method = utils.ToUpper(method)
  198. // Check if the HTTP method is valid unless it's USE
  199. if method != methodUse && app.methodInt(method) == -1 {
  200. panic(fmt.Sprintf("add: invalid http method %s\n", method))
  201. }
  202. // is mounted app
  203. isMount := group != nil && group.app != app
  204. // A route requires atleast one ctx handler
  205. if len(handlers) == 0 && !isMount {
  206. panic(fmt.Sprintf("missing handler in route: %s\n", pathRaw))
  207. }
  208. // Cannot have an empty path
  209. if pathRaw == "" {
  210. pathRaw = "/"
  211. }
  212. // Path always start with a '/'
  213. if pathRaw[0] != '/' {
  214. pathRaw = "/" + pathRaw
  215. }
  216. // Create a stripped path in-case sensitive / trailing slashes
  217. pathPretty := pathRaw
  218. // Case sensitive routing, all to lowercase
  219. if !app.config.CaseSensitive {
  220. pathPretty = utils.ToLower(pathPretty)
  221. }
  222. // Strict routing, remove trailing slashes
  223. if !app.config.StrictRouting && len(pathPretty) > 1 {
  224. pathPretty = utils.TrimRight(pathPretty, '/')
  225. }
  226. // Is layer a middleware?
  227. isUse := method == methodUse
  228. // Is path a direct wildcard?
  229. isStar := pathPretty == "/*"
  230. // Is path a root slash?
  231. isRoot := pathPretty == "/"
  232. // Parse path parameters
  233. parsedRaw := parseRoute(pathRaw)
  234. parsedPretty := parseRoute(pathPretty)
  235. // Create route metadata without pointer
  236. route := Route{
  237. // Router booleans
  238. use: isUse,
  239. mount: isMount,
  240. star: isStar,
  241. root: isRoot,
  242. // Path data
  243. path: RemoveEscapeChar(pathPretty),
  244. routeParser: parsedPretty,
  245. Params: parsedRaw.params,
  246. // Group data
  247. group: group,
  248. // Public data
  249. Path: pathRaw,
  250. Method: method,
  251. Handlers: handlers,
  252. }
  253. // Increment global handler count
  254. atomic.AddUint32(&app.handlersCount, uint32(len(handlers)))
  255. // Middleware route matches all HTTP methods
  256. if isUse {
  257. // Add route to all HTTP methods stack
  258. for _, m := range app.config.RequestMethods {
  259. // Create a route copy to avoid duplicates during compression
  260. r := route
  261. app.addRoute(m, &r, isMount)
  262. }
  263. } else {
  264. // Add route to stack
  265. app.addRoute(method, &route, isMount)
  266. }
  267. return app
  268. }
  269. func (app *App) registerStatic(prefix, root string, config ...Static) Router {
  270. // For security we want to restrict to the current work directory.
  271. if root == "" {
  272. root = "."
  273. }
  274. // Cannot have an empty prefix
  275. if prefix == "" {
  276. prefix = "/"
  277. }
  278. // Prefix always start with a '/' or '*'
  279. if prefix[0] != '/' {
  280. prefix = "/" + prefix
  281. }
  282. // in case sensitive routing, all to lowercase
  283. if !app.config.CaseSensitive {
  284. prefix = utils.ToLower(prefix)
  285. }
  286. // Strip trailing slashes from the root path
  287. if len(root) > 0 && root[len(root)-1] == '/' {
  288. root = root[:len(root)-1]
  289. }
  290. // Is prefix a direct wildcard?
  291. isStar := prefix == "/*"
  292. // Is prefix a root slash?
  293. isRoot := prefix == "/"
  294. // Is prefix a partial wildcard?
  295. if strings.Contains(prefix, "*") {
  296. // /john* -> /john
  297. isStar = true
  298. prefix = strings.Split(prefix, "*")[0]
  299. // Fix this later
  300. }
  301. prefixLen := len(prefix)
  302. if prefixLen > 1 && prefix[prefixLen-1:] == "/" {
  303. // /john/ -> /john
  304. prefixLen--
  305. prefix = prefix[:prefixLen]
  306. }
  307. const cacheDuration = 10 * time.Second
  308. // Fileserver settings
  309. fs := &fasthttp.FS{
  310. Root: root,
  311. AllowEmptyRoot: true,
  312. GenerateIndexPages: false,
  313. AcceptByteRange: false,
  314. Compress: false,
  315. CompressedFileSuffix: app.config.CompressedFileSuffix,
  316. CacheDuration: cacheDuration,
  317. IndexNames: []string{"index.html"},
  318. PathRewrite: func(fctx *fasthttp.RequestCtx) []byte {
  319. path := fctx.Path()
  320. if len(path) >= prefixLen {
  321. if isStar && app.getString(path[0:prefixLen]) == prefix {
  322. path = append(path[0:0], '/')
  323. } else {
  324. path = path[prefixLen:]
  325. if len(path) == 0 || path[len(path)-1] != '/' {
  326. path = append(path, '/')
  327. }
  328. }
  329. }
  330. if len(path) > 0 && path[0] != '/' {
  331. path = append([]byte("/"), path...)
  332. }
  333. return path
  334. },
  335. PathNotFound: func(fctx *fasthttp.RequestCtx) {
  336. fctx.Response.SetStatusCode(StatusNotFound)
  337. },
  338. }
  339. // Set config if provided
  340. var cacheControlValue string
  341. var modifyResponse Handler
  342. if len(config) > 0 {
  343. maxAge := config[0].MaxAge
  344. if maxAge > 0 {
  345. cacheControlValue = "public, max-age=" + strconv.Itoa(maxAge)
  346. }
  347. fs.CacheDuration = config[0].CacheDuration
  348. fs.Compress = config[0].Compress
  349. fs.AcceptByteRange = config[0].ByteRange
  350. fs.GenerateIndexPages = config[0].Browse
  351. if config[0].Index != "" {
  352. fs.IndexNames = []string{config[0].Index}
  353. }
  354. modifyResponse = config[0].ModifyResponse
  355. }
  356. fileHandler := fs.NewRequestHandler()
  357. handler := func(c *Ctx) error {
  358. // Don't execute middleware if Next returns true
  359. if len(config) != 0 && config[0].Next != nil && config[0].Next(c) {
  360. return c.Next()
  361. }
  362. // Serve file
  363. fileHandler(c.fasthttp)
  364. // Sets the response Content-Disposition header to attachment if the Download option is true
  365. if len(config) > 0 && config[0].Download {
  366. c.Attachment()
  367. }
  368. // Return request if found and not forbidden
  369. status := c.fasthttp.Response.StatusCode()
  370. if status != StatusNotFound && status != StatusForbidden {
  371. if len(cacheControlValue) > 0 {
  372. c.fasthttp.Response.Header.Set(HeaderCacheControl, cacheControlValue)
  373. }
  374. if modifyResponse != nil {
  375. return modifyResponse(c)
  376. }
  377. return nil
  378. }
  379. // Reset response to default
  380. c.fasthttp.SetContentType("") // Issue #420
  381. c.fasthttp.Response.SetStatusCode(StatusOK)
  382. c.fasthttp.Response.SetBodyString("")
  383. // Next middleware
  384. return c.Next()
  385. }
  386. // Create route metadata without pointer
  387. route := Route{
  388. // Router booleans
  389. use: true,
  390. root: isRoot,
  391. path: prefix,
  392. // Public data
  393. Method: MethodGet,
  394. Path: prefix,
  395. Handlers: []Handler{handler},
  396. }
  397. // Increment global handler count
  398. atomic.AddUint32(&app.handlersCount, 1)
  399. // Add route to stack
  400. app.addRoute(MethodGet, &route)
  401. // Add HEAD route
  402. app.addRoute(MethodHead, &route)
  403. return app
  404. }
  405. func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
  406. // Check mounted routes
  407. var mounted bool
  408. if len(isMounted) > 0 {
  409. mounted = isMounted[0]
  410. }
  411. // Get unique HTTP method identifier
  412. m := app.methodInt(method)
  413. // prevent identically route registration
  414. l := len(app.stack[m])
  415. 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 {
  416. preRoute := app.stack[m][l-1]
  417. preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
  418. } else {
  419. // Increment global route position
  420. route.pos = atomic.AddUint32(&app.routesCount, 1)
  421. route.Method = method
  422. // Add route to the stack
  423. app.stack[m] = append(app.stack[m], route)
  424. app.routesRefreshed = true
  425. }
  426. // Execute onRoute hooks & change latestRoute if not adding mounted route
  427. if !mounted {
  428. app.mutex.Lock()
  429. app.latestRoute = route
  430. if err := app.hooks.executeOnRouteHooks(*route); err != nil {
  431. panic(err)
  432. }
  433. app.mutex.Unlock()
  434. }
  435. }
  436. // buildTree build the prefix tree from the previously registered routes
  437. func (app *App) buildTree() *App {
  438. if !app.routesRefreshed {
  439. return app
  440. }
  441. // loop all the methods and stacks and create the prefix tree
  442. for m := range app.config.RequestMethods {
  443. tsMap := make(map[string][]*Route)
  444. for _, route := range app.stack[m] {
  445. treePath := ""
  446. if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
  447. treePath = route.routeParser.segs[0].Const[:3]
  448. }
  449. // create tree stack
  450. tsMap[treePath] = append(tsMap[treePath], route)
  451. }
  452. app.treeStack[m] = tsMap
  453. }
  454. // loop the methods and tree stacks and add global stack and sort everything
  455. for m := range app.config.RequestMethods {
  456. tsMap := app.treeStack[m]
  457. for treePart := range tsMap {
  458. if treePart != "" {
  459. // merge global tree routes in current tree stack
  460. tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
  461. }
  462. // sort tree slices with the positions
  463. slc := tsMap[treePart]
  464. sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
  465. }
  466. }
  467. app.routesRefreshed = false
  468. return app
  469. }