router.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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. "strings"
  9. "time"
  10. utils "github.com/gofiber/utils"
  11. fasthttp "github.com/valyala/fasthttp"
  12. )
  13. // Router defines all router handle interface includes app and group router.
  14. type Router interface {
  15. Use(args ...interface{}) Router
  16. Get(path string, handlers ...Handler) Router
  17. Head(path string, handlers ...Handler) Router
  18. Post(path string, handlers ...Handler) Router
  19. Put(path string, handlers ...Handler) Router
  20. Delete(path string, handlers ...Handler) Router
  21. Connect(path string, handlers ...Handler) Router
  22. Options(path string, handlers ...Handler) Router
  23. Trace(path string, handlers ...Handler) Router
  24. Patch(path string, handlers ...Handler) Router
  25. Add(method, path string, handlers ...Handler) Router
  26. Static(prefix, root string, config ...Static) Router
  27. All(path string, handlers ...Handler) Router
  28. Group(prefix string, handlers ...Handler) Router
  29. }
  30. // Route is a struct that holds all metadata for each registered handler
  31. type Route struct {
  32. // Data for routing
  33. pos int // Position in stack -> important for the sort of the matched routes
  34. use bool // USE matches path prefixes
  35. star bool // Path equals '*'
  36. root bool // Path equals '/'
  37. path string // Prettified path
  38. routeParser routeParser // Parameter parser
  39. // Public fields
  40. Method string `json:"method"` // HTTP method
  41. Path string `json:"path"` // Original registered route path
  42. Params []string `json:"params"` // Case sensitive param keys
  43. Handlers []Handler `json:"-"` // Ctx handlers
  44. }
  45. func (r *Route) match(path, original string) (match bool, values []string) {
  46. // root path check
  47. if r.root && path == "/" {
  48. return true, values
  49. // '*' wildcard matches any path
  50. } else if r.star {
  51. values := getAllocFreeParams(1)
  52. if len(original) > 1 {
  53. values[0] = original[1:]
  54. }
  55. return true, values
  56. }
  57. // Does this route have parameters
  58. if len(r.Params) > 0 {
  59. // Match params
  60. if paramPos, match := r.routeParser.getMatch(path, r.use); match {
  61. // Get params from the original path
  62. return match, r.routeParser.paramsForPos(original, paramPos)
  63. }
  64. }
  65. // Is this route a Middleware?
  66. if r.use {
  67. // Single slash will match or path prefix
  68. if r.root || strings.HasPrefix(path, r.path) {
  69. return true, values
  70. }
  71. // Check for a simple path match
  72. } else if len(r.path) == len(path) && r.path == path {
  73. return true, values
  74. }
  75. // No match
  76. return false, values
  77. }
  78. func (app *App) next(ctx *Ctx) bool {
  79. // Get stack length
  80. tree, ok := app.treeStack[ctx.methodINT][ctx.treePath]
  81. if !ok {
  82. tree = app.treeStack[ctx.methodINT][""]
  83. }
  84. lenr := len(tree) - 1
  85. // Loop over the route stack starting from previous index
  86. for ctx.indexRoute < lenr {
  87. // Increment route index
  88. ctx.indexRoute++
  89. // Get *Route
  90. route := tree[ctx.indexRoute]
  91. // Check if it matches the request path
  92. match, values := route.match(ctx.path, ctx.pathOriginal)
  93. // No match, next route
  94. if !match {
  95. continue
  96. }
  97. // Pass route reference and param values
  98. ctx.route = route
  99. // Non use handler matched
  100. if !ctx.matched && !route.use {
  101. ctx.matched = true
  102. }
  103. ctx.values = values
  104. // Execute first handler of route
  105. ctx.indexHandler = 0
  106. route.Handlers[0](ctx)
  107. // Stop scanning the stack
  108. return true
  109. }
  110. // If c.Next() does not match, return 404
  111. ctx.SendStatus(StatusNotFound)
  112. ctx.SendString("Cannot " + ctx.method + " " + ctx.pathOriginal)
  113. // Scan stack for other methods
  114. // Moved from app.handler
  115. // It should be here,
  116. // because middleware may break the route chain
  117. if !ctx.matched {
  118. setMethodNotAllowed(ctx)
  119. }
  120. return false
  121. }
  122. func (app *App) handler(rctx *fasthttp.RequestCtx) {
  123. // Acquire Ctx with fasthttp request from pool
  124. ctx := app.AcquireCtx(rctx)
  125. // handle invalid http method directly
  126. if ctx.methodINT == -1 {
  127. ctx.Status(StatusBadRequest).SendString("Invalid http method")
  128. app.ReleaseCtx(ctx)
  129. return
  130. }
  131. // Find match in stack
  132. match := app.next(ctx)
  133. // Generate ETag if enabled
  134. if match && app.Settings.ETag {
  135. setETag(ctx, false)
  136. }
  137. // Release Ctx
  138. app.ReleaseCtx(ctx)
  139. }
  140. func (app *App) register(method, pathRaw string, handlers ...Handler) Route {
  141. // Uppercase HTTP methods
  142. method = utils.ToUpper(method)
  143. // Check if the HTTP method is valid unless it's USE
  144. if method != methodUse && methodInt(method) == -1 {
  145. panic(fmt.Sprintf("add: invalid http method %s\n", method))
  146. }
  147. // A route requires atleast one ctx handler
  148. if len(handlers) == 0 {
  149. panic(fmt.Sprintf("missing handler in route: %s\n", pathRaw))
  150. }
  151. // Cannot have an empty path
  152. if pathRaw == "" {
  153. pathRaw = "/"
  154. }
  155. // Path always start with a '/'
  156. if pathRaw[0] != '/' {
  157. pathRaw = "/" + pathRaw
  158. }
  159. // Create a stripped path in-case sensitive / trailing slashes
  160. pathPretty := pathRaw
  161. // Case sensitive routing, all to lowercase
  162. if !app.Settings.CaseSensitive {
  163. pathPretty = utils.ToLower(pathPretty)
  164. }
  165. // Strict routing, remove trailing slashes
  166. if !app.Settings.StrictRouting && len(pathPretty) > 1 {
  167. pathPretty = utils.TrimRight(pathPretty, '/')
  168. }
  169. // Is layer a middleware?
  170. var isUse = method == methodUse
  171. // Is path a direct wildcard?
  172. var isStar = pathPretty == "/*"
  173. // Is path a root slash?
  174. var isRoot = pathPretty == "/"
  175. // Parse path parameters
  176. var parsedRaw = parseRoute(pathRaw)
  177. var parsedPretty = parseRoute(pathPretty)
  178. // Create route metadata without pointer
  179. route := Route{
  180. // Router booleans
  181. use: isUse,
  182. star: isStar,
  183. root: isRoot,
  184. // Path data
  185. path: pathPretty,
  186. routeParser: parsedPretty,
  187. Params: parsedRaw.params,
  188. // Public data
  189. Path: pathRaw,
  190. Method: method,
  191. Handlers: handlers,
  192. }
  193. // Increment global handler count
  194. app.mutex.Lock()
  195. app.handlerCount += len(handlers)
  196. app.mutex.Unlock()
  197. // Middleware route matches all HTTP methods
  198. if isUse {
  199. // Add route to all HTTP methods stack
  200. for _, m := range intMethod {
  201. // create a route copy
  202. r := route
  203. app.addRoute(m, &r)
  204. }
  205. return route
  206. }
  207. // Add route to stack
  208. app.addRoute(method, &route)
  209. return route
  210. }
  211. func (app *App) registerStatic(prefix, root string, config ...Static) Route {
  212. // For security we want to restrict to the current work directory.
  213. if len(root) == 0 {
  214. root = "."
  215. }
  216. // Cannot have an empty prefix
  217. if prefix == "" {
  218. prefix = "/"
  219. }
  220. // Prefix always start with a '/' or '*'
  221. if prefix[0] != '/' {
  222. prefix = "/" + prefix
  223. }
  224. // in case sensitive routing, all to lowercase
  225. if !app.Settings.CaseSensitive {
  226. prefix = utils.ToLower(prefix)
  227. }
  228. // Strip trailing slashes from the root path
  229. if len(root) > 0 && root[len(root)-1] == '/' {
  230. root = root[:len(root)-1]
  231. }
  232. // Is prefix a direct wildcard?
  233. var isStar = prefix == "/*"
  234. // Is prefix a root slash?
  235. var isRoot = prefix == "/"
  236. // Is prefix a partial wildcard?
  237. if strings.Contains(prefix, "*") {
  238. // /john* -> /john
  239. isStar = true
  240. prefix = strings.Split(prefix, "*")[0]
  241. // Fix this later
  242. }
  243. prefixLen := len(prefix)
  244. // Fileserver settings
  245. fs := &fasthttp.FS{
  246. Root: root,
  247. GenerateIndexPages: false,
  248. AcceptByteRange: false,
  249. Compress: false,
  250. CompressedFileSuffix: app.Settings.CompressedFileSuffix,
  251. CacheDuration: 10 * time.Second,
  252. IndexNames: []string{"index.html"},
  253. PathRewrite: func(ctx *fasthttp.RequestCtx) []byte {
  254. path := ctx.Path()
  255. if len(path) >= prefixLen {
  256. if isStar && getString(path[0:prefixLen]) == prefix {
  257. path = append(path[0:0], '/')
  258. } else {
  259. path = append(path[prefixLen:], '/')
  260. }
  261. }
  262. if len(path) > 0 && path[0] != '/' {
  263. path = append([]byte("/"), path...)
  264. }
  265. return path
  266. },
  267. PathNotFound: func(ctx *fasthttp.RequestCtx) {
  268. ctx.Response.SetStatusCode(StatusNotFound)
  269. },
  270. }
  271. // Set config if provided
  272. if len(config) > 0 {
  273. fs.Compress = config[0].Compress
  274. fs.AcceptByteRange = config[0].ByteRange
  275. fs.GenerateIndexPages = config[0].Browse
  276. if config[0].Index != "" {
  277. fs.IndexNames = []string{config[0].Index}
  278. }
  279. }
  280. fileHandler := fs.NewRequestHandler()
  281. handler := func(c *Ctx) {
  282. // Serve file
  283. fileHandler(c.Fasthttp)
  284. // Return request if found and not forbidden
  285. status := c.Fasthttp.Response.StatusCode()
  286. if status != StatusNotFound && status != StatusForbidden {
  287. return
  288. }
  289. // Reset response to default
  290. c.Fasthttp.SetContentType("") // Issue #420
  291. c.Fasthttp.Response.SetStatusCode(StatusOK)
  292. c.Fasthttp.Response.SetBodyString("")
  293. // Next middleware
  294. c.Next()
  295. }
  296. // Create route metadata without pointer
  297. route := Route{
  298. // Router booleans
  299. use: true,
  300. root: isRoot,
  301. path: prefix,
  302. // Public data
  303. Method: MethodGet,
  304. Path: prefix,
  305. Handlers: []Handler{handler},
  306. }
  307. // Increment global handler count
  308. app.mutex.Lock()
  309. app.handlerCount++
  310. app.mutex.Unlock()
  311. // Add route to stack
  312. app.addRoute(MethodGet, &route)
  313. // Add HEAD route
  314. headRoute := route
  315. app.addRoute(MethodHead, &headRoute)
  316. return route
  317. }
  318. func (app *App) addRoute(method string, route *Route) {
  319. // Get unique HTTP method indentifier
  320. m := methodInt(method)
  321. // prevent identically route registration
  322. l := len(app.stack[m])
  323. if l > 0 && app.stack[m][l-1].Path == route.Path && route.use == app.stack[m][l-1].use {
  324. preRoute := app.stack[m][l-1]
  325. preRoute.Handlers = append(preRoute.Handlers, route.Handlers...)
  326. } else {
  327. // Increment global route position
  328. app.mutex.Lock()
  329. app.routesCount++
  330. app.mutex.Unlock()
  331. route.pos = app.routesCount
  332. route.Method = method
  333. // Add route to the stack
  334. app.stack[m] = append(app.stack[m], route)
  335. }
  336. }
  337. // buildTree build the prefix tree from the previously registered routes
  338. func (app *App) buildTree() *App {
  339. // loop all the methods and stacks and create the prefix tree
  340. for m := range intMethod {
  341. app.treeStack[m] = make(map[string][]*Route)
  342. for _, route := range app.stack[m] {
  343. treePath := ""
  344. if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 {
  345. treePath = route.routeParser.segs[0].Const[:3]
  346. }
  347. // create tree stack
  348. app.treeStack[m][treePath] = append(app.treeStack[m][treePath], route)
  349. }
  350. }
  351. // loop the methods and tree stacks and add global stack and sort everything
  352. for m := range intMethod {
  353. for treePart := range app.treeStack[m] {
  354. if treePart != "" {
  355. // merge global tree routes in current tree stack
  356. app.treeStack[m][treePart] = uniqueRouteStack(append(app.treeStack[m][treePart], app.treeStack[m][""]...))
  357. }
  358. // sort tree slices with the positions
  359. sort.Slice(app.treeStack[m][treePart], func(i, j int) bool {
  360. return app.treeStack[m][treePart][i].pos < app.treeStack[m][treePart][j].pos
  361. })
  362. }
  363. }
  364. return app
  365. }