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. "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. // ### important: 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. // misc
  188. pos: route.pos,
  189. // Public data
  190. Path: route.Path,
  191. Params: route.Params,
  192. Name: route.Name,
  193. Method: route.Method,
  194. Handlers: route.Handlers,
  195. }
  196. }
  197. func (app *App) register(method, pathRaw string, group *Group, handlers ...Handler) {
  198. // Uppercase HTTP methods
  199. method = utils.ToUpper(method)
  200. // Check if the HTTP method is valid unless it's USE
  201. if method != methodUse && app.methodInt(method) == -1 {
  202. panic(fmt.Sprintf("add: invalid http method %s\n", method))
  203. }
  204. // is mounted app
  205. isMount := group != nil && group.app != app
  206. // A route requires atleast one ctx handler
  207. if len(handlers) == 0 && !isMount {
  208. panic(fmt.Sprintf("missing handler in route: %s\n", pathRaw))
  209. }
  210. // Cannot have an empty path
  211. if pathRaw == "" {
  212. pathRaw = "/"
  213. }
  214. // Path always start with a '/'
  215. if pathRaw[0] != '/' {
  216. pathRaw = "/" + pathRaw
  217. }
  218. // Create a stripped path in-case sensitive / trailing slashes
  219. pathPretty := pathRaw
  220. // Case-sensitive routing, all to lowercase
  221. if !app.config.CaseSensitive {
  222. pathPretty = utils.ToLower(pathPretty)
  223. }
  224. // Strict routing, remove trailing slashes
  225. if !app.config.StrictRouting && len(pathPretty) > 1 {
  226. pathPretty = utils.TrimRight(pathPretty, '/')
  227. }
  228. // Is layer a middleware?
  229. isUse := method == methodUse
  230. // Is path a direct wildcard?
  231. isStar := pathPretty == "/*"
  232. // Is path a root slash?
  233. isRoot := pathPretty == "/"
  234. // Parse path parameters
  235. parsedRaw := parseRoute(pathRaw)
  236. parsedPretty := parseRoute(pathPretty)
  237. // Create route metadata without pointer
  238. route := Route{
  239. // Router booleans
  240. use: isUse,
  241. mount: isMount,
  242. star: isStar,
  243. root: isRoot,
  244. // Path data
  245. path: RemoveEscapeChar(pathPretty),
  246. routeParser: parsedPretty,
  247. Params: parsedRaw.params,
  248. // Group data
  249. group: group,
  250. // Public data
  251. Path: pathRaw,
  252. Method: method,
  253. Handlers: handlers,
  254. }
  255. // Increment global handler count
  256. atomic.AddUint32(&app.handlersCount, uint32(len(handlers)))
  257. // Middleware route matches all HTTP methods
  258. if isUse {
  259. // Add route to all HTTP methods stack
  260. for _, m := range app.config.RequestMethods {
  261. // Create a route copy to avoid duplicates during compression
  262. r := route
  263. app.addRoute(m, &r, isMount)
  264. }
  265. } else {
  266. // Add route to stack
  267. app.addRoute(method, &route, isMount)
  268. }
  269. }
  270. func (app *App) registerStatic(prefix, root string, config ...Static) {
  271. // For security, we want to restrict to the current work directory.
  272. if root == "" {
  273. root = "."
  274. }
  275. // Cannot have an empty prefix
  276. if prefix == "" {
  277. prefix = "/"
  278. }
  279. // Prefix always start with a '/' or '*'
  280. if prefix[0] != '/' {
  281. prefix = "/" + prefix
  282. }
  283. // in case-sensitive routing, all to lowercase
  284. if !app.config.CaseSensitive {
  285. prefix = utils.ToLower(prefix)
  286. }
  287. // Strip trailing slashes from the root path
  288. if len(root) > 0 && root[len(root)-1] == '/' {
  289. root = root[:len(root)-1]
  290. }
  291. // Is prefix a direct wildcard?
  292. isStar := prefix == "/*"
  293. // Is prefix a root slash?
  294. isRoot := prefix == "/"
  295. // Is prefix a partial wildcard?
  296. if strings.Contains(prefix, "*") {
  297. // /john* -> /john
  298. isStar = true
  299. prefix = strings.Split(prefix, "*")[0]
  300. // Fix this later
  301. }
  302. prefixLen := len(prefix)
  303. if prefixLen > 1 && prefix[prefixLen-1:] == "/" {
  304. // /john/ -> /john
  305. prefixLen--
  306. prefix = prefix[:prefixLen]
  307. }
  308. const cacheDuration = 10 * time.Second
  309. // Fileserver settings
  310. fs := &fasthttp.FS{
  311. Root: root,
  312. AllowEmptyRoot: true,
  313. GenerateIndexPages: false,
  314. AcceptByteRange: false,
  315. Compress: false,
  316. CompressedFileSuffix: app.config.CompressedFileSuffix,
  317. CacheDuration: cacheDuration,
  318. IndexNames: []string{"index.html"},
  319. PathRewrite: func(fctx *fasthttp.RequestCtx) []byte {
  320. path := fctx.Path()
  321. if len(path) >= prefixLen {
  322. if isStar && app.getString(path[0:prefixLen]) == prefix {
  323. path = append(path[0:0], '/')
  324. } else {
  325. path = path[prefixLen:]
  326. if len(path) == 0 || path[len(path)-1] != '/' {
  327. path = append(path, '/')
  328. }
  329. }
  330. }
  331. if len(path) > 0 && path[0] != '/' {
  332. path = append([]byte("/"), path...)
  333. }
  334. return path
  335. },
  336. PathNotFound: func(fctx *fasthttp.RequestCtx) {
  337. fctx.Response.SetStatusCode(StatusNotFound)
  338. },
  339. }
  340. // Set config if provided
  341. var cacheControlValue string
  342. var modifyResponse Handler
  343. if len(config) > 0 {
  344. maxAge := config[0].MaxAge
  345. if maxAge > 0 {
  346. cacheControlValue = "public, max-age=" + strconv.Itoa(maxAge)
  347. }
  348. fs.CacheDuration = config[0].CacheDuration
  349. fs.Compress = config[0].Compress
  350. fs.AcceptByteRange = config[0].ByteRange
  351. fs.GenerateIndexPages = config[0].Browse
  352. if config[0].Index != "" {
  353. fs.IndexNames = []string{config[0].Index}
  354. }
  355. modifyResponse = config[0].ModifyResponse
  356. }
  357. fileHandler := fs.NewRequestHandler()
  358. handler := func(c *Ctx) error {
  359. // Don't execute middleware if Next returns true
  360. if len(config) != 0 && config[0].Next != nil && config[0].Next(c) {
  361. return c.Next()
  362. }
  363. // Serve file
  364. fileHandler(c.fasthttp)
  365. // Sets the response Content-Disposition header to attachment if the Download option is true
  366. if len(config) > 0 && config[0].Download {
  367. c.Attachment()
  368. }
  369. // Return request if found and not forbidden
  370. status := c.fasthttp.Response.StatusCode()
  371. if status != StatusNotFound && status != StatusForbidden {
  372. if len(cacheControlValue) > 0 {
  373. c.fasthttp.Response.Header.Set(HeaderCacheControl, cacheControlValue)
  374. }
  375. if modifyResponse != nil {
  376. return modifyResponse(c)
  377. }
  378. return nil
  379. }
  380. // Reset response to default
  381. c.fasthttp.SetContentType("") // Issue #420
  382. c.fasthttp.Response.SetStatusCode(StatusOK)
  383. c.fasthttp.Response.SetBodyString("")
  384. // Next middleware
  385. return c.Next()
  386. }
  387. // Create route metadata without pointer
  388. route := Route{
  389. // Router booleans
  390. use: true,
  391. root: isRoot,
  392. path: prefix,
  393. // Public data
  394. Method: MethodGet,
  395. Path: prefix,
  396. Handlers: []Handler{handler},
  397. }
  398. // Increment global handler count
  399. atomic.AddUint32(&app.handlersCount, 1)
  400. // Add route to stack
  401. app.addRoute(MethodGet, &route)
  402. // Add HEAD route
  403. app.addRoute(MethodHead, &route)
  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. }