router.go 13 KB

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