1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-21 06:53:05 +00:00
fiber/router.go

285 lines
6.8 KiB
Go
Raw Normal View History

// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
2020-02-21 18:06:08 +01:00
2019-12-30 07:29:42 -05:00
package fiber
import (
2020-03-24 12:20:07 +01:00
"log"
"strings"
"time"
2019-12-30 07:29:42 -05:00
2020-03-24 12:20:07 +01:00
fasthttp "github.com/valyala/fasthttp"
2019-12-30 07:29:42 -05:00
)
// Route metadata
type Route struct {
// Internal fields
use bool // USE matches path prefixes
star bool // Path equals '*' or '/*'
root bool // Path equals '/'
parsed parsedParams // parsed contains parsed params segments
2020-02-21 16:54:14 +01:00
// External fields for ctx.Route() method
Path string // Registered route path
Method string // HTTP method
Params []string // Slice containing the params names
Handler func(*Ctx) // Ctx handler
2020-03-03 12:21:34 -05:00
}
2020-02-21 18:07:43 +01:00
2020-03-31 10:03:39 +02:00
func (app *App) nextRoute(ctx *Ctx) {
mINT := methodINT[ctx.method]
// Get stack length
lenr := len(app.routes[mINT]) - 1
// Loop over stack starting from previous index
2020-03-24 12:20:07 +01:00
for ctx.index < lenr {
// Increment stack index
2020-03-24 12:20:07 +01:00
ctx.index++
// Get *Route
route := app.routes[mINT][ctx.index]
// Check if it matches the request path
match, values := route.matchRoute(ctx.path)
// No match, continue
if !match {
continue
2020-03-24 12:20:07 +01:00
}
// Match! Set route and param values to Ctx
ctx.route = route
ctx.values = values
// Execute handler
route.Handler(ctx)
// Generate ETag if enabled
if app.Settings.ETag {
setETag(ctx, false)
}
return
2020-03-24 12:20:07 +01:00
}
// Send a 404 by default if no route is matched
2020-03-24 12:20:07 +01:00
if len(ctx.Fasthttp.Response.Body()) == 0 {
ctx.SendStatus(404)
}
2020-02-21 18:07:43 +01:00
}
2020-03-03 12:21:34 -05:00
func (r *Route) matchRoute(path string) (match bool, values []string) {
// Middleware routes allow prefix matches
if r.use {
// Match any path if wildcard and pass path as param
if r.star {
return true, []string{path}
2020-03-24 12:20:07 +01:00
}
// Match any path if route equals '/'
if r.root {
return true, values
}
// Middleware matches path prefix
2020-03-24 12:20:07 +01:00
if strings.HasPrefix(path, r.Path) {
return true, values
}
// No prefix match, and we do not allow params in app.use
2020-03-24 12:20:07 +01:00
return false, values
}
// '*' wildcard matches any path
if r.star {
return true, []string{path}
}
// Check if a single '/' matches
if r.root && path == "/" {
return true, values
}
// Does this route have parameters
if len(r.Params) > 0 {
// Do we have a match?
params, ok := r.parsed.matchParams(path)
// We have a match!
if ok {
return true, params
2020-03-24 12:20:07 +01:00
}
}
// Check for a simple path match
if len(r.Path) == len(path) && r.Path == path {
return true, values
}
2020-03-24 12:20:07 +01:00
// Nothing match
return false, values
2020-03-04 12:30:29 +01:00
}
2020-03-31 10:03:39 +02:00
func (app *App) handler(fctx *fasthttp.RequestCtx) {
2020-03-24 12:20:07 +01:00
// get fiber context from sync pool
ctx := AcquireCtx(fctx)
defer ReleaseCtx(ctx)
// Attach app poiner to access the routes
2020-03-24 12:20:07 +01:00
ctx.app = app
// Case sensitive routing
if !app.Settings.CaseSensitive {
ctx.path = strings.ToLower(ctx.path)
}
// Strict routing
if !app.Settings.StrictRouting && len(ctx.path) > 1 {
ctx.path = strings.TrimRight(ctx.path, "/")
}
// Find route
app.nextRoute(ctx)
2020-02-27 04:10:26 -05:00
}
2020-03-03 12:21:34 -05:00
2020-03-31 10:03:39 +02:00
func (app *App) registerMethod(method, path string, handlers ...func(*Ctx)) {
2020-03-24 12:20:07 +01:00
// Route requires atleast one handler
if len(handlers) == 0 {
log.Fatalf("Missing handler in route")
}
// Cannot have an empty path
if path == "" {
path = "/"
}
// Path always start with a '/' or '*'
if path[0] != '/' {
2020-03-24 12:20:07 +01:00
path = "/" + path
}
// Store original path to strip case sensitive params
original := path
// Case sensitive routing, all to lowercase
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
// Strict routing, remove last `/`
if !app.Settings.StrictRouting && len(path) > 1 {
path = strings.TrimRight(path, "/")
}
// Set route booleans
var isUse = method == "USE"
2020-03-24 12:20:07 +01:00
// Middleware / All allows all HTTP methods
if isUse || method == "ALL" {
2020-03-24 12:20:07 +01:00
method = "*"
}
var isStar = path == "/*"
2020-03-24 12:20:07 +01:00
// Middleware containing only a `/` equals wildcard
if isUse && path == "/" {
2020-03-24 12:20:07 +01:00
isStar = true
}
var isRoot = path == "/"
2020-03-24 12:20:07 +01:00
// Route properties
var isParsed = parseParams(original)
2020-03-24 12:20:07 +01:00
for i := range handlers {
route := &Route{
use: isUse,
star: isStar,
root: isRoot,
parsed: isParsed,
Path: path,
Method: method,
v1.9.6 (#360) **🚀 Fiber `v1.9.6`** Special thanks to @renanbastos93 & @renewerner87 for optimizing the current router. Help use translate our API documentation by [clicking here](https://crowdin.com/project/gofiber) 🔥 New - `AcquireCtx` / `ReleaseCtx` The Ctx pool is now accessible for third-party packages - Fiber docs merged [Russian](https://docs.gofiber.io/v/ru/) translations **84%** - Fiber docs merged [Spanish](https://docs.gofiber.io/v/es/) translations **65%** - Fiber docs merged [French](https://docs.gofiber.io/v/fr/) translations **40%** - Fiber docs merged [German](https://docs.gofiber.io/v/de/) translations **32%** - Fiber docs merged [Portuguese](https://docs.gofiber.io/v/pt/) translations **24%** 🩹 Fixes - Hotfix for interpolated params in nested routes https://github.com/gofiber/fiber/issues/354 - Some `Ctx` methods didn't work correctly when called without an `*App` pointer. - `ctx.Vary` sometimes added duplicates to the response header - Improved router by ditching regexp and increased performance by **817%** without allocations. ```go // Tested with 350 github API routes Benchmark_Router_OLD-4 614 2467460 ns/op 68902 B/op 600 allocs/op Benchmark_Router_NEW-4 3429 302033 ns/op 0 B/op 0 allocs/op ``` 🧹 Updates - Add context benchmarks - Remove some unnecessary functions from `utils` - Add router & param test cases - Add new coffee supporters to readme - Add third party middlewares to readme - Add more comments to source code - Cleanup some old helper functions 🧬 Middleware - [gofiber/adaptor](https://github.com/gofiber/adaptor) `v0.0.1` Converter for net/http handlers to/from Fiber handlers - [gofiber/session](https://github.com/gofiber/session) `v1.0.0` big improvements and support for storage providers - [gofiber/logger](https://github.com/gofiber/logger) `v0.0.6` supports `${error}` param - [gofiber/embed](https://github.com/gofiber/embed) `v0.0.9` minor improvements and support for directory browsing Co-authored-by: ReneWerner87 <renewerner87@googlemail.com>
2020-05-11 13:42:42 +02:00
Params: isParsed.Params,
Handler: handlers[i],
}
if method == "*" {
// Add handler to all HTTP methods
for m := range methodINT {
app.addRoute(m, route)
}
continue
}
// Add route to stack
app.addRoute(method, route)
// Add route to HEAD method if GET
if method == MethodGet {
app.addRoute(MethodHead, route)
}
2020-03-24 12:20:07 +01:00
}
2019-12-30 13:33:50 +01:00
}
2020-03-03 12:21:34 -05:00
2020-03-31 10:03:39 +02:00
func (app *App) registerStatic(prefix, root string, config ...Static) {
2020-03-24 12:20:07 +01:00
// Cannot have an empty prefix
if prefix == "" {
prefix = "/"
}
// Prefix always start with a '/' or '*'
if prefix[0] != '/' {
2020-03-24 12:20:07 +01:00
prefix = "/" + prefix
}
// Match anything
var wildcard = false
if prefix == "*" || prefix == "/*" {
wildcard = true
prefix = "/"
}
// Case sensitive routing, all to lowercase
if !app.Settings.CaseSensitive {
prefix = strings.ToLower(prefix)
}
// For security we want to restrict to the current work directory.
if len(root) == 0 {
root = "."
}
// Strip trailing slashes from the root path
if len(root) > 0 && root[len(root)-1] == '/' {
root = root[:len(root)-1]
}
// isSlash ?
var isRoot = prefix == "/"
2020-03-24 12:20:07 +01:00
if strings.Contains(prefix, "*") {
wildcard = true
prefix = strings.Split(prefix, "*")[0]
}
var stripper = len(prefix)
if isRoot {
2020-03-24 12:20:07 +01:00
stripper = 0
}
// Fileserver settings
fs := &fasthttp.FS{
Root: root,
GenerateIndexPages: false,
AcceptByteRange: false,
Compress: false,
CompressedFileSuffix: ".fiber.gz",
CacheDuration: 10 * time.Second,
IndexNames: []string{"index.html"},
PathRewrite: fasthttp.NewPathPrefixStripper(stripper),
PathNotFound: func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetStatusCode(404)
ctx.Response.SetBodyString("Not Found")
},
}
// Set config if provided
if len(config) > 0 {
fs.Compress = config[0].Compress
fs.AcceptByteRange = config[0].ByteRange
fs.GenerateIndexPages = config[0].Browse
if config[0].Index != "" {
fs.IndexNames = []string{config[0].Index}
}
}
fileHandler := fs.NewRequestHandler()
route := &Route{
use: true,
root: isRoot,
Method: "*",
Path: prefix,
2020-03-24 12:20:07 +01:00
Handler: func(c *Ctx) {
// Do stuff
if wildcard {
c.Fasthttp.Request.SetRequestURI(prefix)
}
// Serve file
fileHandler(c.Fasthttp)
// Finish request if found and not forbidden
status := c.Fasthttp.Response.StatusCode()
if status != 404 && status != 403 {
return
2020-03-24 12:20:07 +01:00
}
// Reset response
c.Fasthttp.Response.Reset()
// Next middleware
2020-03-24 12:20:07 +01:00
c.Next()
},
}
// Add route to stack
app.addRoute(MethodGet, route)
app.addRoute(MethodHead, route)
}
func (app *App) addRoute(method string, route *Route) {
m := methodINT[method]
app.routes[m] = append(app.routes[m], route)
2019-12-30 07:29:42 -05:00
}