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

372 lines
9.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-07-15 17:43:30 +02:00
"fmt"
2020-03-24 12:20:07 +01:00
"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{}) Router
Get(path string, handlers ...Handler) Router
Head(path string, handlers ...Handler) Router
Post(path string, handlers ...Handler) Router
Put(path string, handlers ...Handler) Router
Delete(path string, handlers ...Handler) Router
Connect(path string, handlers ...Handler) Router
Options(path string, handlers ...Handler) Router
Trace(path string, handlers ...Handler) Router
Patch(path string, handlers ...Handler) Router
Add(method, path string, handlers ...Handler) Router
Static(prefix, root string, config ...Static) Router
All(path string, handlers ...Handler) Router
Group(prefix string, handlers ...Handler) Router
}
// 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
use bool // USE matches path prefixes
star bool // Path equals '*'
root bool // Path equals '/'
path string // Prettified path
routeParser routeParser // Parameter parser
// Public fields
2020-07-02 20:26:38 +02:00
Method string `json:"method"` // HTTP method
Path string `json:"path"` // Original registered route path
2020-07-05 11:17:42 +02:00
Params []string `json:"params"` // Case sensitive param keys
2020-07-02 20:26:38 +02:00
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
2020-07-05 11:17:42 +02:00
if len(r.Params) > 0 {
2020-05-24 14:01:52 +02:00
// 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
// Non use handler matched
if !ctx.matched && !route.use {
ctx.matched = true
}
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
ctx.SendStatus(StatusNotFound)
2020-06-01 13:36:51 +02:00
ctx.SendString("Cannot " + ctx.method + " " + ctx.pathOriginal)
// Scan stack for other methods
// Moved from app.handler
// It should be here,
// because middleware may break the route chain
if !ctx.matched {
setMethodNotAllowed(ctx)
}
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-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
2020-07-19 13:36:26 +02:00
func (app *App) register(method, pathRaw string, handlers ...Handler) {
// Uppercase HTTP methods
method = utils.ToUpper(method)
// Check if the HTTP method is valid unless it's USE
if methodInt(method) == -1 {
2020-07-15 17:43:30 +02:00
panic(fmt.Sprintf("add: invalid http method %s\n", method))
}
// A route requires atleast one ctx handler
2020-03-24 12:20:07 +01:00
if len(handlers) == 0 {
2020-07-15 17:43:30 +02:00
panic(fmt.Sprintf("missing handler in route: %s\n", 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 == methodUse
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)
// Create route metadata
route := &Route{
// Router booleans
use: isUse,
star: isStar,
root: isRoot,
// Path data
path: pathPretty,
routeParser: parsedPretty,
2020-07-05 11:17:42 +02:00
Params: parsedRaw.params,
// Public data
Path: pathRaw,
Method: method,
Handlers: handlers,
}
app.mutex.Lock()
2020-06-06 07:31:01 +02:00
app.addRoute(method, route)
app.mutex.Unlock()
2019-12-30 13:33:50 +01:00
}
2020-03-03 12:21:34 -05:00
2020-07-19 13:36:26 +02:00
func (app *App) registerStatic(prefix, root string, config ...Static) {
// 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(StatusNotFound)
2020-03-24 12:20:07 +01:00
},
}
// 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 != StatusNotFound && status != StatusForbidden {
return
}
// Reset response to default
c.Fasthttp.SetContentType("") // Issue #420
c.Fasthttp.Response.SetStatusCode(StatusOK)
c.Fasthttp.Response.SetBodyString("")
// Next middleware
2020-06-03 17:16:10 +02:00
c.Next()
}
route := &Route{
use: true,
root: isRoot,
path: prefix,
Method: MethodGet,
Path: prefix,
}
route.Handlers = append(route.Handlers, handler)
// Add route to stack
app.mutex.Lock()
app.addRoute(MethodGet, route, true)
app.mutex.Unlock()
}
2020-06-20 17:26:48 +02:00
func (app *App) compressed(route *Route, isStatic ...bool) bool {
if route.Method == methodUse && len(isStatic) == 0 {
// Check if stack tail is the same use route
for m := range intMethod {
end := len(app.stack[m]) - 1
if end == -1 {
return false
}
if app.stack[m][end].Path != route.Path {
return false
}
}
// Append handlers directly
for m := range intMethod {
end := len(app.stack[m]) - 1
app.stack[m][end].Handlers = append(app.stack[m][end].Handlers, route.Handlers...)
}
route.Handlers = nil
return true
}
m := methodInt(route.Method)
end := len(app.stack[m]) - 1
if end == -1 {
return false
}
if app.stack[m][end].Method != route.Method || app.stack[m][end].Path != route.Path {
return false
}
app.stack[m][end].Handlers = append(app.stack[m][end].Handlers, route.Handlers...)
route.Handlers = nil
return true
}
func (app *App) addRoute(method string, route *Route, isStatic ...bool) {
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])
}
if !app.compressed(route, isStatic...) {
// Get unique HTTP method indentifier
m := methodInt(method)
if route.use && len(isStatic) == 0 {
// Add route to all methods' stack
for m := range intMethod {
app.stack[m] = append(app.stack[m], route)
}
} else {
// Add route to the specific method's stack
app.stack[m] = append(app.stack[m], route)
// Handle GET routes on HEAD requests
if method == MethodGet {
app.stack[1] = append(app.stack[1], route)
}
}
}
// Add route to routes slice
app.routes = append(app.routes, route)
2019-12-30 07:29:42 -05:00
}