router.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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. "html"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "sync/atomic"
  12. "time"
  13. "github.com/gofiber/fiber/v2/utils"
  14. "github.com/valyala/fasthttp"
  15. )
  16. // Router defines all router handle interface, including app and group router.
  17. type Router interface {
  18. Use(args ...interface{}) Router
  19. Get(path string, handlers ...Handler) Router
  20. Head(path string, handlers ...Handler) Router
  21. Post(path string, handlers ...Handler) Router
  22. Put(path string, handlers ...Handler) Router
  23. Delete(path string, handlers ...Handler) Router
  24. Connect(path string, handlers ...Handler) Router
  25. Options(path string, handlers ...Handler) Router
  26. Trace(path string, handlers ...Handler) Router
  27. Patch(path string, handlers ...Handler) Router
  28. Add(method, path string, handlers ...Handler) Router
  29. Static(prefix, root string, config ...Static) Router
  30. All(path string, handlers ...Handler) Router
  31. Group(prefix string, handlers ...Handler) Router
  32. Route(prefix string, fn func(router Router), name ...string) Router
  33. Mount(prefix string, fiber *App) Router
  34. Name(name string) Router
  35. }
  36. // Route is a struct that holds all metadata for each registered handler.
  37. type Route struct {
  38. // always keep in sync with the copy method "app.copyRoute"
  39. // Data for routing
  40. pos uint32 // Position in stack -> important for the sort of the matched routes
  41. use bool // USE matches path prefixes
  42. mount bool // Indicated a mounted app on a specific route
  43. star bool // Path equals '*'
  44. root bool // Path equals '/'
  45. path string // Prettified path
  46. routeParser routeParser // Parameter parser
  47. group *Group // Group instance. used for routes in groups
  48. // Public fields
  49. Method string `json:"method"` // HTTP method
  50. Name string `json:"name"` // Route's name
  51. //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine
  52. Path string `json:"path"` // Original registered route path
  53. Params []string `json:"params"` // Case sensitive param keys
  54. Handlers []Handler `json:"-"` // Ctx handlers
  55. }
  56. func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {
  57. // root detectionPath check
  58. if r.root && detectionPath == "/" {
  59. return true
  60. // '*' wildcard matches any detectionPath
  61. } else if r.star {
  62. if len(path) > 1 {
  63. params[0] = path[1:]
  64. } else {
  65. params[0] = ""
  66. }
  67. return true
  68. }
  69. // Does this route have parameters
  70. if len(r.Params) > 0 {
  71. // Match params
  72. if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match {
  73. // Get params from the path detectionPath
  74. return match
  75. }
  76. }
  77. // Is this route a Middleware?
  78. if r.use {
  79. // Single slash will match or detectionPath prefix
  80. if r.root || strings.HasPrefix(detectionPath, r.path) {
  81. return true
  82. }
  83. // Check for a simple detectionPath match
  84. } else if len(r.path) == len(detectionPath) && r.path == detectionPath {
  85. return true
  86. }
  87. // No match
  88. return false
  89. }
  90. func (app *App) next(c *Ctx) (bool, error) {
  91. // Get stack length
  92. tree, ok := app.treeStack[c.methodINT][c.treePath]
  93. if !ok {
  94. tree = app.treeStack[c.methodINT][""]
  95. }
  96. lenTree := len(tree) - 1
  97. // Loop over the route stack starting from previous index
  98. for c.indexRoute < lenTree {
  99. // Increment route index
  100. c.indexRoute++
  101. // Get *Route
  102. route := tree[c.indexRoute]
  103. var match bool
  104. var err error
  105. // skip for mounted apps
  106. if route.mount {
  107. continue
  108. }
  109. // Check if it matches the request path
  110. match = route.match(c.detectionPath, c.path, &c.values)
  111. if !match {
  112. // No match, next route
  113. continue
  114. }
  115. // Pass route reference and param values
  116. c.route = route
  117. // Non use handler matched
  118. if !c.matched && !route.use {
  119. c.matched = true
  120. }
  121. // Execute first handler of route
  122. c.indexHandler = 0
  123. if len(route.Handlers) > 0 {
  124. err = route.Handlers[0](c)
  125. }
  126. return match, err // Stop scanning the stack
  127. }
  128. // If c.Next() does not match, return 404
  129. err := NewError(StatusNotFound, "Cannot "+c.method+" "+html.EscapeString(c.pathOriginal))
  130. if !c.matched && app.methodExist(c) {
  131. // If no match, scan stack again if other methods match the request
  132. // Moved from app.handler because middleware may break the route chain
  133. err = ErrMethodNotAllowed
  134. }
  135. return false, err
  136. }
  137. 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
  138. // Acquire Ctx with fasthttp request from pool
  139. c := app.AcquireCtx(rctx)
  140. defer app.ReleaseCtx(c)
  141. // handle invalid http method directly
  142. if c.methodINT == -1 {
  143. _ = c.Status(StatusBadRequest).SendString("Invalid http method") //nolint:errcheck // It is fine to ignore the error here
  144. return
  145. }
  146. // Find match in stack
  147. match, err := app.next(c)
  148. if err != nil {
  149. if catch := c.app.ErrorHandler(c, err); catch != nil {
  150. _ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here
  151. }
  152. // TODO: Do we need to return here?
  153. }
  154. // Generate ETag if enabled
  155. if match && app.config.ETag {
  156. setETag(c, false)
  157. }
  158. }
  159. func (app *App) addPrefixToRoute(prefix string, route *Route) *Route {
  160. prefixedPath := getGroupPath(prefix, route.Path)
  161. prettyPath := prefixedPath
  162. // Case-sensitive routing, all to lowercase
  163. if !app.config.CaseSensitive {
  164. prettyPath = utils.ToLower(prettyPath)
  165. }
  166. // Strict routing, remove trailing slashes
  167. if !app.config.StrictRouting && len(prettyPath) > 1 {
  168. prettyPath = utils.TrimRight(prettyPath, '/')
  169. }
  170. route.Path = prefixedPath
  171. route.path = RemoveEscapeChar(prettyPath)
  172. route.routeParser = parseRoute(prettyPath)
  173. route.root = false
  174. route.star = false
  175. return route
  176. }
  177. func (*App) copyRoute(route *Route) *Route {
  178. return &Route{
  179. // Router booleans
  180. use: route.use,
  181. mount: route.mount,
  182. star: route.star,
  183. root: route.root,
  184. // Path data
  185. path: route.path,
  186. routeParser: route.routeParser,
  187. Params: route.Params,
  188. // misc
  189. pos: route.pos,
  190. // Public data
  191. Path: route.Path,
  192. Method: route.Method,
  193. Handlers: route.Handlers,
  194. }
  195. }
  196. func (app *App) register(method, pathRaw string, group *Group, handlers ...Handler) {
  197. // Uppercase HTTP methods
  198. method = utils.ToUpper(method)
  199. // Check if the HTTP method is valid unless it's USE
  200. if method != methodUse && app.methodInt(method) == -1 {
  201. panic(fmt.Sprintf("add: invalid http method %s\n", method))
  202. }
  203. // is mounted app
  204. isMount := group != nil && group.app != app
  205. // A route requires atleast one ctx handler
  206. if len(handlers) == 0 && !isMount {
  207. panic(fmt.Sprintf("missing handler in route: %s\n", pathRaw))
  208. }
  209. // Cannot have an empty path
  210. if pathRaw == "" {
  211. pathRaw = "/"
  212. }
  213. // Path always start with a '/'
  214. if pathRaw[0] != '/' {
  215. pathRaw = "/" + pathRaw
  216. }
  217. // Create a stripped path in-case sensitive / trailing slashes
  218. pathPretty := pathRaw
  219. // Case-sensitive routing, all to lowercase
  220. if !app.config.CaseSensitive {
  221. pathPretty = utils.ToLower(pathPretty)
  222. }
  223. // Strict routing, remove trailing slashes
  224. if !app.config.StrictRouting && len(pathPretty) > 1 {
  225. pathPretty = utils.TrimRight(pathPretty, '/')
  226. }
  227. // Is layer a middleware?
  228. isUse := method == methodUse
  229. // Is path a direct wildcard?
  230. isStar := pathPretty == "/*"
  231. // Is path a root slash?
  232. isRoot := pathPretty == "/"
  233. // Parse path parameters
  234. parsedRaw := parseRoute(pathRaw)
  235. parsedPretty := parseRoute(pathPretty)
  236. // Create route metadata without pointer
  237. route := Route{
  238. // Router booleans
  239. use: isUse,
  240. mount: isMount,
  241. star: isStar,
  242. root: isRoot,
  243. // Path data
  244. path: RemoveEscapeChar(pathPretty),
  245. routeParser: parsedPretty,
  246. Params: parsedRaw.params,
  247. // Group data
  248. group: group,
  249. // Public data
  250. Path: pathRaw,
  251. Method: method,
  252. Handlers: handlers,
  253. }
  254. // Increment global handler count
  255. atomic.AddUint32(&app.handlersCount, uint32(len(handlers)))
  256. // Middleware route matches all HTTP methods
  257. if isUse {
  258. // Add route to all HTTP methods stack
  259. for _, m := range app.config.RequestMethods {
  260. // Create a route copy to avoid duplicates during compression
  261. r := route
  262. app.addRoute(m, &r, isMount)
  263. }
  264. } else {
  265. // Add route to stack
  266. app.addRoute(method, &route, isMount)
  267. }
  268. }
  269. func (app *App) registerStatic(prefix, root string, config ...Static) {
  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. }
  404. func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
  405. // Check mounted routes
  406. var mounted bool
  407. if len(isMounted) > 0 {
  408. mounted = isMounted[0]
  409. }
  410. // Get unique HTTP method identifier
  411. m := app.methodInt(method)
  412. // prevent identically route registration
  413. l := len(app.stack[m])
  414. 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 {
  415. preRoute := app.stack[m][l-1]
  416. preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
  417. } else {
  418. // Increment global route position
  419. route.pos = atomic.AddUint32(&app.routesCount, 1)
  420. route.Method = method
  421. // Add route to the stack
  422. app.stack[m] = append(app.stack[m], route)
  423. app.routesRefreshed = true
  424. }
  425. // Execute onRoute hooks & change latestRoute if not adding mounted route
  426. if !mounted {
  427. app.mutex.Lock()
  428. app.latestRoute = route
  429. if err := app.hooks.executeOnRouteHooks(*route); err != nil {
  430. panic(err)
  431. }
  432. app.mutex.Unlock()
  433. }
  434. }
  435. // buildTree build the prefix tree from the previously registered routes
  436. func (app *App) buildTree() *App {
  437. if !app.routesRefreshed {
  438. return app
  439. }
  440. // loop all the methods and stacks and create the prefix tree
  441. for m := range app.config.RequestMethods {
  442. tsMap := make(map[string][]*Route)
  443. for _, route := range app.stack[m] {
  444. treePath := ""
  445. if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
  446. treePath = route.routeParser.segs[0].Const[:3]
  447. }
  448. // create tree stack
  449. tsMap[treePath] = append(tsMap[treePath], route)
  450. }
  451. app.treeStack[m] = tsMap
  452. }
  453. // loop the methods and tree stacks and add global stack and sort everything
  454. for m := range app.config.RequestMethods {
  455. tsMap := app.treeStack[m]
  456. for treePart := range tsMap {
  457. if treePart != "" {
  458. // merge global tree routes in current tree stack
  459. tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...))
  460. }
  461. // sort tree slices with the positions
  462. slc := tsMap[treePart]
  463. sort.Slice(slc, func(i, j int) bool { return slc[i].pos < slc[j].pos })
  464. }
  465. }
  466. app.routesRefreshed = false
  467. return app
  468. }