1
0
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:
Fenny 2020-05-23 09:29:35 +02:00
parent 14283c765b
commit 8e91e848da

267
router.go
View File

@ -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)
}