2020-05-07 19:28:21 +02:00
|
|
|
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
|
2020-05-07 21:02:24 +02:00
|
|
|
// 🤖 Github Repository: https://github.com/gofiber/fiber
|
2020-05-07 19:28:21 +02:00
|
|
|
// 📌 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-05-23 09:29:35 +02: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
|
|
|
)
|
|
|
|
|
2020-06-17 15:09:08 +08: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
|
|
|
|
}
|
|
|
|
|
2020-05-23 09:29:35 +02:00
|
|
|
// Route is a struct that holds all metadata for each registered handler
|
|
|
|
type Route struct {
|
|
|
|
// Data for routing
|
2020-06-20 17:26:48 +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
|
|
|
|
allowedMethods string // Methods that are allowed on this route
|
|
|
|
routeParser routeParser // Parameter parser
|
|
|
|
routeParams []string // Case sensitive param keys
|
2020-05-23 09:29:35 +02:00
|
|
|
|
|
|
|
// Public fields
|
2020-05-24 10:02:21 -04:00
|
|
|
Path string // Original registered route path
|
|
|
|
Method string // HTTP method
|
|
|
|
Handlers []Handler // Ctx handlers
|
2020-05-23 09:29:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2020-05-23 09:29:35 +02:00
|
|
|
// 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
|
2020-05-23 09:29:35 +02:00
|
|
|
}
|
|
|
|
// 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
|
2020-05-23 09:29:35 +02:00
|
|
|
}
|
|
|
|
// No match
|
|
|
|
return false, values
|
|
|
|
}
|
|
|
|
|
2020-05-16 05:14:01 +02:00
|
|
|
func (app *App) next(ctx *Ctx) bool {
|
|
|
|
// TODO set unique INT within handler(), not here over and over again
|
|
|
|
method := methodINT[ctx.method]
|
2020-05-07 19:28:21 +02:00
|
|
|
// Get stack length
|
2020-05-16 05:14:01 +02:00
|
|
|
lenr := len(app.stack[method]) - 1
|
2020-05-23 09:29:35 +02:00
|
|
|
// Loop over the route stack starting from previous index
|
2020-05-27 22:54:09 +02:00
|
|
|
for ctx.indexRoute < lenr {
|
|
|
|
// Increment route index
|
|
|
|
ctx.indexRoute++
|
2020-05-07 19:28:21 +02:00
|
|
|
// Get *Route
|
2020-05-27 22:54:09 +02:00
|
|
|
route := app.stack[method][ctx.indexRoute]
|
2020-05-07 19:28:21 +02:00
|
|
|
// Check if it matches the request path
|
2020-05-23 09:29:35 +02:00
|
|
|
match, values := route.match(ctx.path, ctx.pathOriginal)
|
|
|
|
// No match, next route
|
2020-05-07 19:28:21 +02:00
|
|
|
if !match {
|
|
|
|
continue
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-23 09:29:35 +02:00
|
|
|
// Pass route reference and param values
|
|
|
|
ctx.route = route
|
2020-05-07 19:28:21 +02:00
|
|
|
ctx.values = values
|
2020-05-27 22:54:09 +02:00
|
|
|
// Execute first handler of route
|
|
|
|
ctx.indexHandler = 0
|
|
|
|
route.Handlers[0](ctx)
|
2020-05-23 09:29:35 +02:00
|
|
|
// 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
|
2020-05-23 09:29:35 +02:00
|
|
|
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 {
|
2020-05-23 09:29:35 +02:00
|
|
|
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
|
2020-05-23 09:29:35 +02:00
|
|
|
app.ReleaseCtx(ctx)
|
2020-02-27 04:10:26 -05:00
|
|
|
}
|
2020-03-03 12:21:34 -05:00
|
|
|
|
2020-05-24 10:02:21 -04:00
|
|
|
func (app *App) register(method, pathRaw string, handlers ...Handler) *Route {
|
2020-05-23 09:29:35 +02:00
|
|
|
// Uppercase HTTP methods
|
|
|
|
method = utils.ToUpper(method)
|
|
|
|
// Check if the HTTP method is valid unless it's USE
|
|
|
|
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 {
|
2020-05-23 09:29:35 +02:00
|
|
|
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
|
2020-05-23 09:29:35 +02:00
|
|
|
if pathRaw == "" {
|
|
|
|
pathRaw = "/"
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-16 05:14:01 +02:00
|
|
|
// Path always start with a '/'
|
2020-05-23 09:29:35 +02:00
|
|
|
if pathRaw[0] != '/' {
|
|
|
|
pathRaw = "/" + pathRaw
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-23 09:29:35 +02: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 {
|
2020-05-23 09:29:35 +02:00
|
|
|
pathPretty = utils.ToLower(pathPretty)
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-23 09:29:35 +02: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?
|
2020-05-07 19:28:21 +02:00
|
|
|
var isUse = method == "USE"
|
2020-05-16 05:14:01 +02:00
|
|
|
// Is path a direct wildcard?
|
2020-05-23 09:29:35 +02:00
|
|
|
var isStar = pathPretty == "/*"
|
2020-05-16 05:14:01 +02:00
|
|
|
// Is path a root slash?
|
2020-05-23 09:29:35 +02:00
|
|
|
var isRoot = pathPretty == "/"
|
2020-05-16 05:14:01 +02:00
|
|
|
// Parse path parameters
|
2020-05-23 09:29:35 +02:00
|
|
|
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()
|
2020-05-23 09:29:35 +02:00
|
|
|
// Create route metadata
|
|
|
|
route := &Route{
|
|
|
|
// Router booleans
|
2020-06-06 07:31:01 +02:00
|
|
|
pos: app.routes,
|
2020-05-23 09:29:35 +02:00
|
|
|
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
|
|
|
|
for m := range methodINT {
|
|
|
|
app.addRoute(m, route)
|
2020-05-07 19:28:21 +02:00
|
|
|
}
|
2020-05-23 09:29:35 +02:00
|
|
|
return route
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:31:01 +02:00
|
|
|
// Handle GET routes on HEAD requests
|
2020-05-23 09:29:35 +02:00
|
|
|
if method == MethodGet {
|
|
|
|
app.addRoute(MethodHead, route)
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-23 09:29:35 +02:00
|
|
|
|
2020-06-06 07:31:01 +02:00
|
|
|
// Add route to stack
|
|
|
|
app.addRoute(method, route)
|
|
|
|
|
2020-05-23 09:29:35 +02:00
|
|
|
return route
|
2019-12-30 13:33:50 +01:00
|
|
|
}
|
2020-03-03 12:21:34 -05:00
|
|
|
|
2020-05-23 09:29:35 +02:00
|
|
|
func (app *App) registerStatic(prefix, root string, config ...Static) *Route {
|
2020-05-27 22:54:09 +02:00
|
|
|
// 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 '*'
|
2020-05-07 20:22:26 +02:00
|
|
|
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 {
|
2020-05-23 09:29:35 +02:00
|
|
|
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]
|
|
|
|
}
|
2020-05-27 22:54:09 +02:00
|
|
|
// Is prefix a direct wildcard?
|
|
|
|
var isStar = prefix == "/*"
|
|
|
|
// Is prefix a root slash?
|
2020-05-07 19:28:21 +02:00
|
|
|
var isRoot = prefix == "/"
|
2020-05-27 22:54:09 +02:00
|
|
|
// Is prefix a partial wildcard?
|
2020-03-24 12:20:07 +01:00
|
|
|
if strings.Contains(prefix, "*") {
|
2020-05-27 22:54:09 +02:00
|
|
|
// /john* -> /john
|
|
|
|
isStar = true
|
2020-03-24 12:20:07 +01:00
|
|
|
prefix = strings.Split(prefix, "*")[0]
|
2020-05-27 22:54:09 +02:00
|
|
|
// Fix this later
|
2020-03-24 12:20:07 +01:00
|
|
|
}
|
2020-05-27 22:54:09 +02: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"},
|
2020-05-27 22:54:09 +02:00
|
|
|
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], '/')
|
2020-05-27 22:54:09 +02:00
|
|
|
} else {
|
2020-06-03 21:35:49 +02:00
|
|
|
path = append(path[prefixLen:], '/')
|
2020-05-27 22:54:09 +02:00
|
|
|
}
|
|
|
|
}
|
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-05-27 22:54:09 +02:00
|
|
|
},
|
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()
|
2020-05-23 09:29:35 +02:00
|
|
|
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
|
2020-06-01 12:54:23 +02:00
|
|
|
c.Fasthttp.SetContentType("") // Issue #420
|
2020-05-23 09:29:35 +02:00
|
|
|
c.Fasthttp.Response.SetStatusCode(200)
|
|
|
|
c.Fasthttp.Response.SetBodyString("")
|
|
|
|
// Next middleware
|
2020-06-03 17:16:10 +02:00
|
|
|
c.Next()
|
2020-05-23 09:29:35 +02:00
|
|
|
}
|
2020-06-06 07:31:01 +02:00
|
|
|
// Increment global route position
|
|
|
|
app.mutex.Lock()
|
|
|
|
app.routes++
|
|
|
|
app.mutex.Unlock()
|
2020-05-23 09:29:35 +02:00
|
|
|
route := &Route{
|
2020-06-06 07:31:01 +02:00
|
|
|
pos: app.routes,
|
2020-05-07 19:28:21 +02:00
|
|
|
use: true,
|
|
|
|
root: isRoot,
|
2020-06-02 23:25:06 +02:00
|
|
|
path: prefix,
|
2020-05-23 09:29:35 +02:00
|
|
|
Method: MethodGet,
|
2020-05-07 19:28:21 +02:00
|
|
|
Path: prefix,
|
|
|
|
}
|
2020-05-23 09:29:35 +02:00
|
|
|
route.Handlers = append(route.Handlers, handler)
|
|
|
|
// Add route to stack
|
|
|
|
app.addRoute(MethodGet, route)
|
|
|
|
app.addRoute(MethodHead, route)
|
|
|
|
return route
|
2020-05-07 19:28:21 +02:00
|
|
|
}
|
2020-06-20 17:26:48 +02:00
|
|
|
|
2020-05-23 09:29:35 +02:00
|
|
|
func (app *App) addRoute(method string, route *Route) {
|
2020-05-16 05:14:01 +02:00
|
|
|
// Get unique HTTP method indentifier
|
2020-05-07 19:28:21 +02:00
|
|
|
m := methodINT[method]
|
2020-05-23 09:29:35 +02:00
|
|
|
// Add route to the stack
|
|
|
|
app.stack[m] = append(app.stack[m], route)
|
2020-06-20 17:26:48 +02:00
|
|
|
|
|
|
|
// Add route to method allowed slice
|
|
|
|
app.stack[9] = append(app.stack[9], route)
|
2019-12-30 07:29:42 -05:00
|
|
|
}
|