1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-19 13:47:54 +00:00
fiber/router.go

330 lines
8.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
utils "github.com/gofiber/utils"
2020-03-24 12:20:07 +01:00
fasthttp "github.com/valyala/fasthttp"
2019-12-30 07:29:42 -05:00
)
// Router defines all router handle interface includes app and group router.
type Router interface {
Use(args ...interface{}) *Route
Get(path string, handlers ...Handler) *Route
Head(path string, handlers ...Handler) *Route
Post(path string, handlers ...Handler) *Route
Put(path string, handlers ...Handler) *Route
Delete(path string, handlers ...Handler) *Route
Connect(path string, handlers ...Handler) *Route
Options(path string, handlers ...Handler) *Route
Trace(path string, handlers ...Handler) *Route
Patch(path string, handlers ...Handler) *Route
Add(method, path string, handlers ...Handler) *Route
Static(prefix, root string, config ...Static) *Route
All(path string, handlers ...Handler) []*Route
Group(prefix string, handlers ...Handler) *Group
}
// Route is a struct that holds all metadata for each registered handler
type Route struct {
// Data for routing
2020-06-20 17:27:40 +02:00
pos int // Position in stack
use bool // USE matches path prefixes
star bool // Path equals '*'
root bool // Path equals '/'
path string // Prettified path
routeParser routeParser // Parameter parser
routeParams []string // Case sensitive param keys
// Public fields
2020-07-02 20:26:38 +02:00
Method string `json:"method"` // HTTP method
Path string `json:"path"` // Original registered route path
Name string `json:"name"` // Name of first handler used in route
Handlers []Handler `json:"-"` // Ctx handlers
}
func (r *Route) match(path, original string) (match bool, values []string) {
2020-05-24 14:47:32 +02:00
// root path check
if r.root && path == "/" {
return true, values
// '*' wildcard matches any path
} else if r.star {
values := getAllocFreeParams(1)
values[0] = original[1:]
return true, values
}
2020-05-24 14:01:52 +02:00
// Does this route have parameters
if len(r.routeParams) > 0 {
// Match params
if paramPos, match := r.routeParser.getMatch(path, r.use); match {
// Get params from the original path
return match, r.routeParser.paramsForPos(original, paramPos)
}
}
// Is this route a Middleware?
if r.use {
// Single slash will match or path prefix
if r.root || strings.HasPrefix(path, r.path) {
2020-05-24 14:01:52 +02:00
return true, values
}
// Check for a simple path match
} else if len(r.path) == len(path) && r.path == path {
2020-05-24 14:01:52 +02:00
return true, values
}
// No match
return false, values
}
2020-05-16 05:14:01 +02:00
func (app *App) next(ctx *Ctx) bool {
// Get stack length
2020-06-30 00:27:28 +02:00
lenr := len(app.stack[ctx.methodINT]) - 1
// Loop over the route stack starting from previous index
for ctx.indexRoute < lenr {
// Increment route index
ctx.indexRoute++
// Get *Route
2020-06-30 00:27:28 +02:00
route := app.stack[ctx.methodINT][ctx.indexRoute]
// Check if it matches the request path
match, values := route.match(ctx.path, ctx.pathOriginal)
// No match, next route
if !match {
continue
2020-03-24 12:20:07 +01:00
}
// Pass route reference and param values
ctx.route = route
ctx.values = values
// Execute first handler of route
ctx.indexHandler = 0
route.Handlers[0](ctx)
// Stop scanning the stack
2020-05-16 05:14:01 +02:00
return true
2020-03-24 12:20:07 +01:00
}
2020-06-01 13:45:04 +02:00
// If c.Next() does not match, return 404
2020-06-01 13:36:51 +02:00
ctx.SendStatus(404)
ctx.SendString("Cannot " + ctx.method + " " + ctx.pathOriginal)
2020-05-16 05:14:01 +02:00
return false
2020-03-04 12:30:29 +01:00
}
2020-05-16 05:14:01 +02:00
func (app *App) handler(rctx *fasthttp.RequestCtx) {
// Acquire Ctx with fasthttp request from pool
ctx := app.AcquireCtx(rctx)
// Prettify path
ctx.prettifyPath()
2020-05-16 05:14:01 +02:00
// Find match in stack
match := app.next(ctx)
2020-06-01 13:45:04 +02:00
// Generate ETag if enabled
if match && app.Settings.ETag {
setETag(ctx, false)
2020-03-24 12:20:07 +01:00
}
2020-06-20 17:26:48 +02:00
// Scan stack for other methods
if !match {
setMethodNotAllowed(ctx)
}
2020-05-16 05:14:01 +02:00
// Release Ctx
app.ReleaseCtx(ctx)
2020-02-27 04:10:26 -05:00
}
2020-03-03 12:21:34 -05:00
func (app *App) register(method, pathRaw string, handlers ...Handler) *Route {
// Uppercase HTTP methods
method = utils.ToUpper(method)
// Check if the HTTP method is valid unless it's USE
2020-06-29 22:51:41 +02:00
if method != "USE" && methodInt(method) == 0 && method != MethodGet {
log.Fatalf("Add: Invalid HTTP method %s", method)
}
// A route requires atleast one ctx handler
2020-03-24 12:20:07 +01:00
if len(handlers) == 0 {
log.Fatalf("Missing func(c *fiber.Ctx) handler in route: %s", pathRaw)
2020-03-24 12:20:07 +01:00
}
// Cannot have an empty path
if pathRaw == "" {
pathRaw = "/"
2020-03-24 12:20:07 +01:00
}
2020-05-16 05:14:01 +02:00
// Path always start with a '/'
if pathRaw[0] != '/' {
pathRaw = "/" + pathRaw
2020-03-24 12:20:07 +01:00
}
// Create a stripped path in-case sensitive / trailing slashes
pathPretty := pathRaw
2020-03-24 12:20:07 +01:00
// Case sensitive routing, all to lowercase
if !app.Settings.CaseSensitive {
pathPretty = utils.ToLower(pathPretty)
2020-03-24 12:20:07 +01:00
}
// Strict routing, remove trailing slashes
if !app.Settings.StrictRouting && len(pathPretty) > 1 {
pathPretty = utils.TrimRight(pathPretty, '/')
2020-03-24 12:20:07 +01:00
}
2020-05-16 05:14:01 +02:00
// Is layer a middleware?
var isUse = method == "USE"
2020-05-16 05:14:01 +02:00
// Is path a direct wildcard?
var isStar = pathPretty == "/*"
2020-05-16 05:14:01 +02:00
// Is path a root slash?
var isRoot = pathPretty == "/"
2020-05-16 05:14:01 +02:00
// Parse path parameters
var parsedRaw = parseRoute(pathRaw)
var parsedPretty = parseRoute(pathPretty)
2020-06-06 07:31:01 +02:00
// Increment global route position
app.mutex.Lock()
app.routes++
app.mutex.Unlock()
// Create route metadata
route := &Route{
// Router booleans
2020-06-06 07:31:01 +02:00
pos: app.routes,
use: isUse,
star: isStar,
root: isRoot,
// Path data
path: pathPretty,
routeParser: parsedPretty,
routeParams: parsedRaw.params,
// Public data
Path: pathRaw,
Method: method,
Handlers: handlers,
}
// Middleware route matches all HTTP methods
if isUse {
// Add route to all HTTP methods stack
2020-06-29 22:51:41 +02:00
for _, m := range intMethod {
app.addRoute(m, route)
}
return route
}
2020-06-06 07:31:01 +02:00
// Handle GET routes on HEAD requests
if method == MethodGet {
app.addRoute(MethodHead, route)
2020-03-24 12:20:07 +01:00
}
2020-06-06 07:31:01 +02:00
// Add route to stack
app.addRoute(method, route)
return route
2019-12-30 13:33:50 +01:00
}
2020-03-03 12:21:34 -05:00
func (app *App) registerStatic(prefix, root string, config ...Static) *Route {
// For security we want to restrict to the current work directory.
if len(root) == 0 {
root = "."
}
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
}
2020-05-16 05:14:01 +02:00
// in case sensitive routing, all to lowercase
2020-03-24 12:20:07 +01:00
if !app.Settings.CaseSensitive {
prefix = utils.ToLower(prefix)
2020-03-24 12:20:07 +01:00
}
// Strip trailing slashes from the root path
if len(root) > 0 && root[len(root)-1] == '/' {
root = root[:len(root)-1]
}
// Is prefix a direct wildcard?
var isStar = prefix == "/*"
// Is prefix a root slash?
var isRoot = prefix == "/"
// Is prefix a partial wildcard?
2020-03-24 12:20:07 +01:00
if strings.Contains(prefix, "*") {
// /john* -> /john
isStar = true
2020-03-24 12:20:07 +01:00
prefix = strings.Split(prefix, "*")[0]
// Fix this later
2020-03-24 12:20:07 +01:00
}
prefixLen := len(prefix)
2020-03-24 12:20:07 +01:00
// Fileserver settings
fs := &fasthttp.FS{
Root: root,
GenerateIndexPages: false,
AcceptByteRange: false,
Compress: false,
2020-06-07 20:35:41 +02:00
CompressedFileSuffix: app.Settings.CompressedFileSuffix,
2020-03-24 12:20:07 +01:00
CacheDuration: 10 * time.Second,
IndexNames: []string{"index.html"},
PathRewrite: func(ctx *fasthttp.RequestCtx) []byte {
path := ctx.Path()
if len(path) >= prefixLen {
if isStar && getString(path[0:prefixLen]) == prefix {
2020-06-03 21:35:49 +02:00
path = append(path[0:0], '/')
} else {
2020-06-03 21:35:49 +02:00
path = append(path[prefixLen:], '/')
}
}
2020-06-03 21:35:49 +02:00
if len(path) > 0 && path[0] != '/' {
path = append([]byte("/"), path...)
}
2020-06-03 17:16:10 +02:00
return path
},
2020-03-24 12:20:07 +01:00
PathNotFound: func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetStatusCode(404)
},
}
// 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()
handler := func(c *Ctx) {
// Serve file
fileHandler(c.Fasthttp)
// Return request if found and not forbidden
status := c.Fasthttp.Response.StatusCode()
if status != 404 && status != 403 {
return
}
// Reset response to default
c.Fasthttp.SetContentType("") // Issue #420
c.Fasthttp.Response.SetStatusCode(200)
c.Fasthttp.Response.SetBodyString("")
// Next middleware
2020-06-03 17:16:10 +02:00
c.Next()
}
2020-06-06 07:31:01 +02:00
// Increment global route position
app.mutex.Lock()
app.routes++
app.mutex.Unlock()
route := &Route{
2020-06-06 07:31:01 +02:00
pos: app.routes,
use: true,
root: isRoot,
path: prefix,
Method: MethodGet,
Path: prefix,
}
route.Handlers = append(route.Handlers, handler)
// Add route to stack
app.addRoute(MethodGet, route)
app.addRoute(MethodHead, route)
return route
}
2020-06-20 17:26:48 +02:00
func (app *App) addRoute(method string, route *Route) {
2020-06-25 16:24:21 +02:00
// Give name to route if not defined
if route.Name == "" && len(route.Handlers) > 0 {
route.Name = utils.FunctionName(route.Handlers[0])
}
2020-05-16 05:14:01 +02:00
// Get unique HTTP method indentifier
2020-06-29 22:51:41 +02:00
m := methodInt(method)
// Add route to the stack
app.stack[m] = append(app.stack[m], route)
2019-12-30 07:29:42 -05:00
}