mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-19 14:07:53 +00:00
Fix case sensitivity params
- Improve performance - Remove allocations - Return case sensitive params Co-Authored-By: RW <renewerner87@googlemail.com>
This commit is contained in:
parent
14283c765b
commit
8e91e848da
267
router.go
267
router.go
@ -9,32 +9,88 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
utils "github.com/gofiber/utils"
|
||||
fasthttp "github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// Route is a struct that holds all metadata for each registered handler
|
||||
type Route struct {
|
||||
// Data for routing
|
||||
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
|
||||
Path string // Original registered route path
|
||||
Method string // HTTP method
|
||||
Handlers []func(*Ctx) // Ctx handlers
|
||||
}
|
||||
|
||||
func (r *Route) match(path, original string) (match bool, values []string) {
|
||||
// Is this route a Middleware?
|
||||
if r.use {
|
||||
// Single slash will match or path prefix
|
||||
if r.root || strings.HasPrefix(path, r.path) {
|
||||
return true, values
|
||||
}
|
||||
// Check for a simple path match
|
||||
} else if len(r.path) == len(path) && r.path == path {
|
||||
return true, values
|
||||
// Middleware routes allow prefix matches
|
||||
} else if r.root && path == "/" {
|
||||
return true, values
|
||||
}
|
||||
// '*' wildcard matches any path
|
||||
if r.star {
|
||||
return true, []string{original}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// No match
|
||||
return false, values
|
||||
}
|
||||
|
||||
func (app *App) next(ctx *Ctx) bool {
|
||||
// TODO set unique INT within handler(), not here over and over again
|
||||
method := methodINT[ctx.method]
|
||||
// Get stack length
|
||||
lenr := len(app.stack[method]) - 1
|
||||
// Loop over the layer stack starting from previous index
|
||||
// Loop over the route stack starting from previous index
|
||||
for ctx.index < lenr {
|
||||
// Increment stack index
|
||||
ctx.index++
|
||||
// Get *Route
|
||||
layer := app.stack[method][ctx.index]
|
||||
route := app.stack[method][ctx.index]
|
||||
// Check if it matches the request path
|
||||
match, values := layer.match(ctx.path)
|
||||
// No match, continue
|
||||
match, values := route.match(ctx.path, ctx.pathOriginal)
|
||||
// No match, next route
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
// Pass layer and param values to Ctx
|
||||
ctx.layer = layer
|
||||
// Pass route reference and param values
|
||||
ctx.route = route
|
||||
ctx.values = values
|
||||
// Execute Ctx handler
|
||||
layer.Handler(ctx)
|
||||
// Stop looping the stack
|
||||
// Loop trough all handlers
|
||||
for i := range route.Handlers {
|
||||
// Execute ctx handler
|
||||
route.Handlers[i](ctx)
|
||||
// Stop if c.Next() is not called
|
||||
if !ctx.next {
|
||||
break
|
||||
}
|
||||
// Reset next bool
|
||||
ctx.next = false
|
||||
}
|
||||
// Stop scanning the stack
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -42,99 +98,97 @@ func (app *App) next(ctx *Ctx) bool {
|
||||
|
||||
func (app *App) handler(rctx *fasthttp.RequestCtx) {
|
||||
// Acquire Ctx with fasthttp request from pool
|
||||
ctx := AcquireCtx(rctx)
|
||||
// Attach app poiner to access the routes
|
||||
ctx.app = app
|
||||
// Attach fasthttp RequestCtx
|
||||
ctx.Fasthttp = rctx
|
||||
// In case sensitive routing, all to lowercase
|
||||
if !app.Settings.CaseSensitive {
|
||||
ctx.path = toLower(ctx.path)
|
||||
}
|
||||
// Strict routing
|
||||
if !app.Settings.StrictRouting && len(ctx.path) > 1 && ctx.path[len(ctx.path)-1] == '/' {
|
||||
ctx.path = trimRight(ctx.path, '/')
|
||||
}
|
||||
ctx := app.AcquireCtx(rctx)
|
||||
// Prettify path
|
||||
ctx.prettifyPath()
|
||||
// Find match in stack
|
||||
match := app.next(ctx)
|
||||
// Generate ETag if enabled
|
||||
if app.Settings.ETag {
|
||||
setETag(ctx, false)
|
||||
}
|
||||
// Send a 404 by default if no layer matched
|
||||
// Send a 404 by default if no route matched
|
||||
if !match {
|
||||
ctx.SendStatus(404)
|
||||
} else if app.Settings.ETag {
|
||||
// Generate ETag if enabled
|
||||
setETag(ctx, false)
|
||||
}
|
||||
// Release Ctx
|
||||
ReleaseCtx(ctx)
|
||||
app.ReleaseCtx(ctx)
|
||||
}
|
||||
|
||||
func (app *App) register(method, path string, handlers ...func(*Ctx)) *App {
|
||||
// A layer requires atleast one ctx handler
|
||||
func (app *App) register(method, pathRaw string, handlers ...func(*Ctx)) *Route {
|
||||
// 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
|
||||
if len(handlers) == 0 {
|
||||
log.Fatalf("Missing handler in route")
|
||||
log.Fatalf("Missing func(c *fiber.Ctx) handler in route: %s", pathRaw)
|
||||
}
|
||||
// Cannot have an empty path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
if pathRaw == "" {
|
||||
pathRaw = "/"
|
||||
}
|
||||
// Path always start with a '/'
|
||||
if path[0] != '/' {
|
||||
path = "/" + path
|
||||
if pathRaw[0] != '/' {
|
||||
pathRaw = "/" + pathRaw
|
||||
}
|
||||
// Store original path to strip case sensitive params
|
||||
original := path
|
||||
// Create a stripped path in-case sensitive / trailing slashes
|
||||
pathPretty := pathRaw
|
||||
// Case sensitive routing, all to lowercase
|
||||
if !app.Settings.CaseSensitive {
|
||||
path = toLower(path)
|
||||
pathPretty = utils.ToLower(pathPretty)
|
||||
}
|
||||
// Strict routing, remove last `/`
|
||||
if !app.Settings.StrictRouting && len(path) > 1 {
|
||||
path = trimRight(path, '/')
|
||||
// Strict routing, remove trailing slashes
|
||||
if !app.Settings.StrictRouting && len(pathPretty) > 1 {
|
||||
pathPretty = utils.TrimRight(pathPretty, '/')
|
||||
}
|
||||
// Is layer a middleware?
|
||||
var isUse = method == "USE"
|
||||
// Is path a direct wildcard?
|
||||
var isStar = path == "/*"
|
||||
var isStar = pathPretty == "/*"
|
||||
// Is path a root slash?
|
||||
var isRoot = path == "/"
|
||||
var isRoot = pathPretty == "/"
|
||||
// Parse path parameters
|
||||
var isParsed = getParams(original)
|
||||
// Loop over handlers
|
||||
for i := range handlers {
|
||||
// Set layer metadata
|
||||
layer := &Layer{
|
||||
// Internals
|
||||
use: isUse,
|
||||
star: isStar,
|
||||
root: isRoot,
|
||||
parsed: isParsed,
|
||||
// Externals
|
||||
Path: path,
|
||||
Method: method,
|
||||
Params: isParsed.params,
|
||||
Handler: handlers[i],
|
||||
}
|
||||
// Middleware layer matches all HTTP methods
|
||||
if isUse {
|
||||
// Add layer to all HTTP methods stack
|
||||
for m := range methodINT {
|
||||
app.addLayer(m, layer)
|
||||
}
|
||||
// Skip to next handler
|
||||
continue
|
||||
}
|
||||
// Add layer to stack
|
||||
app.addLayer(method, layer)
|
||||
// Also add GET layer to HEAD
|
||||
if method == MethodGet {
|
||||
app.addLayer(MethodHead, layer)
|
||||
}
|
||||
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,
|
||||
routeParams: parsedRaw.params,
|
||||
|
||||
// Public data
|
||||
Path: pathRaw,
|
||||
Method: method,
|
||||
Handlers: handlers,
|
||||
}
|
||||
return app
|
||||
// Middleware route matches all HTTP methods
|
||||
if isUse {
|
||||
// Add route to all HTTP methods stack
|
||||
for m := range methodINT {
|
||||
app.addRoute(m, route)
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
// Add route to stack
|
||||
app.addRoute(method, route)
|
||||
// Also add GET routes to HEAD stack
|
||||
if method == MethodGet {
|
||||
app.addRoute(MethodHead, route)
|
||||
}
|
||||
|
||||
return route
|
||||
}
|
||||
|
||||
func (app *App) registerStatic(prefix, root string, config ...Static) {
|
||||
func (app *App) registerStatic(prefix, root string, config ...Static) *Route {
|
||||
// Cannot have an empty prefix
|
||||
if prefix == "" {
|
||||
prefix = "/"
|
||||
@ -151,7 +205,7 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
|
||||
}
|
||||
// in case sensitive routing, all to lowercase
|
||||
if !app.Settings.CaseSensitive {
|
||||
prefix = toLower(prefix)
|
||||
prefix = utils.ToLower(prefix)
|
||||
}
|
||||
// For security we want to restrict to the current work directory.
|
||||
if len(root) == 0 {
|
||||
@ -183,7 +237,6 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
|
||||
PathRewrite: fasthttp.NewPathPrefixStripper(stripper),
|
||||
PathNotFound: func(ctx *fasthttp.RequestCtx) {
|
||||
ctx.Response.SetStatusCode(404)
|
||||
ctx.Response.SetBodyString("Not Found")
|
||||
},
|
||||
}
|
||||
// Set config if provided
|
||||
@ -196,37 +249,45 @@ func (app *App) registerStatic(prefix, root string, config ...Static) {
|
||||
}
|
||||
}
|
||||
fileHandler := fs.NewRequestHandler()
|
||||
layer := &Layer{
|
||||
handler := func(c *Ctx) {
|
||||
// Do stuff
|
||||
if wildcard {
|
||||
c.Fasthttp.Request.SetRequestURI(prefix)
|
||||
}
|
||||
// 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.Response.SetStatusCode(200)
|
||||
c.Fasthttp.Response.SetBodyString("")
|
||||
// Next middleware
|
||||
match := c.app.next(c)
|
||||
// If no other route is executed return 404 Not Found
|
||||
if !match {
|
||||
c.Fasthttp.Response.SetStatusCode(404)
|
||||
c.Fasthttp.Response.SetBodyString("Not Found")
|
||||
}
|
||||
}
|
||||
route := &Route{
|
||||
use: true,
|
||||
root: isRoot,
|
||||
Method: "*",
|
||||
Method: MethodGet,
|
||||
Path: prefix,
|
||||
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
|
||||
}
|
||||
// Reset response
|
||||
c.Fasthttp.Response.Reset()
|
||||
// Next middleware
|
||||
c.Next()
|
||||
},
|
||||
}
|
||||
// Add layer to stack
|
||||
app.addLayer(MethodGet, layer)
|
||||
app.addLayer(MethodHead, layer)
|
||||
route.Handlers = append(route.Handlers, handler)
|
||||
// Add route to stack
|
||||
app.addRoute(MethodGet, route)
|
||||
app.addRoute(MethodHead, route)
|
||||
return route
|
||||
}
|
||||
|
||||
func (app *App) addLayer(method string, layer *Layer) {
|
||||
func (app *App) addRoute(method string, route *Route) {
|
||||
// Get unique HTTP method indentifier
|
||||
m := methodINT[method]
|
||||
// Add layer to the stack
|
||||
app.stack[m] = append(app.stack[m], layer)
|
||||
// Add route to the stack
|
||||
app.stack[m] = append(app.stack[m], route)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user