mirror of https://github.com/gofiber/fiber.git
🧹 chore: Improve Performance of Fiber Router (#3261)
* Initial improvements * Update test * Improve RemoveEscapeChar performance * Fix lint issues * Re-add comments * Add dedicated request handlers * Fix lint issues * Add test case for app.All with custom method * Add test for custom Ctx and Request Methods * Simplify test logic * Simplify testpull/3266/head
parent
775e0a73f3
commit
845a7f8b8e
|
@ -37,4 +37,4 @@ jobs:
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
# NOTE: Keep this in sync with the version from .golangci.yml
|
# NOTE: Keep this in sync with the version from .golangci.yml
|
||||||
version: v1.62.0
|
version: v1.62.2
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -35,7 +35,7 @@ markdown:
|
||||||
## lint: 🚨 Run lint checks
|
## lint: 🚨 Run lint checks
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run ./...
|
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 run ./...
|
||||||
|
|
||||||
## test: 🚦 Execute all tests
|
## test: 🚦 Execute all tests
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
16
app.go
16
app.go
|
@ -616,6 +616,10 @@ func (app *App) handleTrustedProxy(ipAddress string) {
|
||||||
// Note: It doesn't allow adding new methods, only customizing exist methods.
|
// Note: It doesn't allow adding new methods, only customizing exist methods.
|
||||||
func (app *App) NewCtxFunc(function func(app *App) CustomCtx) {
|
func (app *App) NewCtxFunc(function func(app *App) CustomCtx) {
|
||||||
app.newCtxFunc = function
|
app.newCtxFunc = function
|
||||||
|
|
||||||
|
if app.server != nil {
|
||||||
|
app.server.Handler = app.customRequestHandler
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCustomConstraint allows to register custom constraint.
|
// RegisterCustomConstraint allows to register custom constraint.
|
||||||
|
@ -868,7 +872,11 @@ func (app *App) Config() Config {
|
||||||
func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476
|
func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476
|
||||||
// prepare the server for the start
|
// prepare the server for the start
|
||||||
app.startupProcess()
|
app.startupProcess()
|
||||||
return app.requestHandler
|
|
||||||
|
if app.newCtxFunc != nil {
|
||||||
|
return app.customRequestHandler
|
||||||
|
}
|
||||||
|
return app.defaultRequestHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stack returns the raw router stack.
|
// Stack returns the raw router stack.
|
||||||
|
@ -1057,7 +1065,11 @@ func (app *App) init() *App {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fasthttp server settings
|
// fasthttp server settings
|
||||||
app.server.Handler = app.requestHandler
|
if app.newCtxFunc != nil {
|
||||||
|
app.server.Handler = app.customRequestHandler
|
||||||
|
} else {
|
||||||
|
app.server.Handler = app.defaultRequestHandler
|
||||||
|
}
|
||||||
app.server.Name = app.config.ServerHeader
|
app.server.Name = app.config.ServerHeader
|
||||||
app.server.Concurrency = app.config.Concurrency
|
app.server.Concurrency = app.config.Concurrency
|
||||||
app.server.NoDefaultDate = app.config.DisableDefaultDate
|
app.server.NoDefaultDate = app.config.DisableDefaultDate
|
||||||
|
|
49
app_test.go
49
app_test.go
|
@ -581,32 +581,51 @@ func Test_App_Use_StrictRouting(t *testing.T) {
|
||||||
|
|
||||||
func Test_App_Add_Method_Test(t *testing.T) {
|
func Test_App_Add_Method_Test(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
require.Equal(t, "add: invalid http method JANE\n", fmt.Sprintf("%v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
|
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
|
||||||
app := New(Config{
|
app := New(Config{
|
||||||
RequestMethods: methods,
|
RequestMethods: methods,
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Add([]string{"JOHN"}, "/doe", testEmptyHandler)
|
app.Add([]string{"JOHN"}, "/john", testEmptyHandler)
|
||||||
|
|
||||||
|
resp, err := app.Test(httptest.NewRequest("JOHN", "/john", nil))
|
||||||
|
require.NoError(t, err, "app.Test(req)")
|
||||||
|
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest(MethodGet, "/john", nil))
|
||||||
|
require.NoError(t, err, "app.Test(req)")
|
||||||
|
require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/john", nil))
|
||||||
|
require.NoError(t, err, "app.Test(req)")
|
||||||
|
require.Equal(t, StatusNotImplemented, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
// Add a new method
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_App_All_Method_Test(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
|
||||||
|
app := New(Config{
|
||||||
|
RequestMethods: methods,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add a new method with All
|
||||||
|
app.All("/doe", testEmptyHandler)
|
||||||
|
|
||||||
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
|
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
|
||||||
require.NoError(t, err, "app.Test(req)")
|
require.NoError(t, err, "app.Test(req)")
|
||||||
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
resp, err = app.Test(httptest.NewRequest(MethodGet, "/doe", nil))
|
// Add a new method
|
||||||
require.NoError(t, err, "app.Test(req)")
|
require.Panics(t, func() {
|
||||||
require.Equal(t, StatusMethodNotAllowed, resp.StatusCode, "Status code")
|
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
|
||||||
|
})
|
||||||
resp, err = app.Test(httptest.NewRequest("UNKNOWN", "/doe", nil))
|
|
||||||
require.NoError(t, err, "app.Test(req)")
|
|
||||||
require.Equal(t, StatusNotImplemented, resp.StatusCode, "Status code")
|
|
||||||
|
|
||||||
app.Add([]string{"JANE"}, "/doe", testEmptyHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// go test -run Test_App_GETOnly
|
// go test -run Test_App_GETOnly
|
||||||
|
|
|
@ -107,8 +107,7 @@ func parseToStruct(aliasTag string, out any, data map[string][]string) error {
|
||||||
func parseToMap(ptr any, data map[string][]string) error {
|
func parseToMap(ptr any, data map[string][]string) error {
|
||||||
elem := reflect.TypeOf(ptr).Elem()
|
elem := reflect.TypeOf(ptr).Elem()
|
||||||
|
|
||||||
//nolint:exhaustive // it's not necessary to check all types
|
switch elem.Kind() { //nolint:exhaustive // it's not necessary to check all types
|
||||||
switch elem.Kind() {
|
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
newMap, ok := ptr.(map[string][]string)
|
newMap, ok := ptr.(map[string][]string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -129,7 +128,6 @@ func parseToMap(ptr any, data map[string][]string) error {
|
||||||
newMap[k] = ""
|
newMap[k] = ""
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
newMap[k] = v[len(v)-1]
|
newMap[k] = v[len(v)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,5 +350,8 @@ type Ctx interface {
|
||||||
setIndexRoute(route int)
|
setIndexRoute(route int)
|
||||||
setMatched(matched bool)
|
setMatched(matched bool)
|
||||||
setRoute(route *Route)
|
setRoute(route *Route)
|
||||||
|
// Drop closes the underlying connection without sending any response headers or body.
|
||||||
|
// This can be useful for silently terminating client connections, such as in DDoS mitigation
|
||||||
|
// or when blocking access to sensitive endpoints.
|
||||||
Drop() error
|
Drop() error
|
||||||
}
|
}
|
||||||
|
|
29
ctx_test.go
29
ctx_test.go
|
@ -127,6 +127,35 @@ func Test_Ctx_CustomCtx(t *testing.T) {
|
||||||
require.Equal(t, "prefix_v3", string(body))
|
require.Equal(t, "prefix_v3", string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_CustomCtx
|
||||||
|
func Test_Ctx_CustomCtx_and_Method(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create app with custom request methods
|
||||||
|
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
|
||||||
|
app := New(Config{
|
||||||
|
RequestMethods: methods,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create custom context
|
||||||
|
app.NewCtxFunc(func(app *App) CustomCtx {
|
||||||
|
return &customCtx{
|
||||||
|
DefaultCtx: *NewDefaultCtx(app),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add route with custom method
|
||||||
|
app.Add([]string{"JOHN"}, "/doe", testEmptyHandler)
|
||||||
|
resp, err := app.Test(httptest.NewRequest("JOHN", "/doe", nil))
|
||||||
|
require.NoError(t, err, "app.Test(req)")
|
||||||
|
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
|
||||||
|
|
||||||
|
// Add a new method
|
||||||
|
require.Panics(t, func() {
|
||||||
|
app.Add([]string{"JANE"}, "/jane", testEmptyHandler)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_Accepts_EmptyAccept
|
// go test -run Test_Ctx_Accepts_EmptyAccept
|
||||||
func Test_Ctx_Accepts_EmptyAccept(t *testing.T) {
|
func Test_Ctx_Accepts_EmptyAccept(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
12
path.go
12
path.go
|
@ -620,10 +620,16 @@ func GetTrimmedParam(param string) string {
|
||||||
|
|
||||||
// RemoveEscapeChar remove escape characters
|
// RemoveEscapeChar remove escape characters
|
||||||
func RemoveEscapeChar(word string) string {
|
func RemoveEscapeChar(word string) string {
|
||||||
if strings.IndexByte(word, escapeChar) != -1 {
|
b := []byte(word)
|
||||||
return strings.ReplaceAll(word, string(escapeChar), "")
|
dst := 0
|
||||||
|
for src := 0; src < len(b); src++ {
|
||||||
|
if b[src] == '\\' {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return word
|
b[dst] = b[src]
|
||||||
|
dst++
|
||||||
|
}
|
||||||
|
return string(b[:dst])
|
||||||
}
|
}
|
||||||
|
|
||||||
func getParamConstraintType(constraintPart string) TypeConstraint {
|
func getParamConstraintType(constraintPart string) TypeConstraint {
|
||||||
|
|
163
router.go
163
router.go
|
@ -5,11 +5,11 @@
|
||||||
package fiber
|
package fiber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gofiber/utils/v2"
|
"github.com/gofiber/utils/v2"
|
||||||
|
@ -65,10 +65,12 @@ type Route struct {
|
||||||
|
|
||||||
func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {
|
func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool {
|
||||||
// root detectionPath check
|
// root detectionPath check
|
||||||
if r.root && detectionPath == "/" {
|
if r.root && len(detectionPath) == 1 && detectionPath[0] == '/' {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// '*' wildcard matches any detectionPath
|
// '*' wildcard matches any detectionPath
|
||||||
} else if r.star {
|
if r.star {
|
||||||
if len(path) > 1 {
|
if len(path) > 1 {
|
||||||
params[0] = path[1:]
|
params[0] = path[1:]
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,24 +78,32 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Does this route have parameters
|
|
||||||
|
// Does this route have parameters?
|
||||||
if len(r.Params) > 0 {
|
if len(r.Params) > 0 {
|
||||||
// Match params
|
// Match params using precomputed routeParser
|
||||||
if match := r.routeParser.getMatch(detectionPath, path, params, r.use); match {
|
if r.routeParser.getMatch(detectionPath, path, params, r.use) {
|
||||||
// Get params from the path detectionPath
|
return true
|
||||||
return match
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Is this route a Middleware?
|
|
||||||
|
// Middleware route?
|
||||||
if r.use {
|
if r.use {
|
||||||
// Single slash will match or detectionPath prefix
|
// Single slash or prefix match
|
||||||
if r.root || strings.HasPrefix(detectionPath, r.path) {
|
plen := len(r.path)
|
||||||
|
if r.root {
|
||||||
|
// If r.root is '/', it matches everything starting at '/'
|
||||||
|
if len(detectionPath) > 0 && detectionPath[0] == '/' {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Check for a simple detectionPath match
|
} else if len(detectionPath) >= plen && detectionPath[:plen] == r.path {
|
||||||
} else if len(r.path) == len(detectionPath) && r.path == detectionPath {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else if len(r.path) == len(detectionPath) && detectionPath == r.path {
|
||||||
|
// Check exact match
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -201,44 +211,63 @@ func (app *App) next(c *DefaultCtx) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) requestHandler(rctx *fasthttp.RequestCtx) {
|
func (app *App) defaultRequestHandler(rctx *fasthttp.RequestCtx) {
|
||||||
// Handler for default ctxs
|
// Acquire DefaultCtx from the pool
|
||||||
var c CustomCtx
|
ctx, ok := app.AcquireCtx(rctx).(*DefaultCtx)
|
||||||
var ok bool
|
|
||||||
if app.newCtxFunc != nil {
|
|
||||||
c, ok = app.AcquireCtx(rctx).(CustomCtx)
|
|
||||||
if !ok {
|
|
||||||
panic(errors.New("requestHandler: failed to type-assert to CustomCtx"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c, ok = app.AcquireCtx(rctx).(*DefaultCtx)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(errors.New("requestHandler: failed to type-assert to *DefaultCtx"))
|
panic(errors.New("requestHandler: failed to type-assert to *DefaultCtx"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
defer app.ReleaseCtx(c)
|
|
||||||
|
|
||||||
// handle invalid http method directly
|
defer app.ReleaseCtx(ctx)
|
||||||
if app.methodInt(c.Method()) == -1 {
|
|
||||||
_ = c.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
|
// Check if the HTTP method is valid
|
||||||
|
if ctx.methodINT == -1 {
|
||||||
|
_ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check flash messages
|
// Optional: Check flash messages
|
||||||
if strings.Contains(utils.UnsafeString(c.Request().Header.RawHeaders()), FlashCookieName) {
|
rawHeaders := ctx.Request().Header.RawHeaders()
|
||||||
c.Redirect().parseAndClearFlashMessages()
|
if len(rawHeaders) > 0 && bytes.Contains(rawHeaders, []byte(FlashCookieName)) {
|
||||||
|
ctx.Redirect().parseAndClearFlashMessages()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find match in stack
|
// Attempt to match a route and execute the chain
|
||||||
var err error
|
_, err := app.next(ctx)
|
||||||
if app.newCtxFunc != nil {
|
|
||||||
_, err = app.nextCustom(c)
|
|
||||||
} else {
|
|
||||||
_, err = app.next(c.(*DefaultCtx)) //nolint:errcheck // It is fine to ignore the error here
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if catch := c.App().ErrorHandler(c, err); catch != nil {
|
if catch := ctx.App().ErrorHandler(ctx, err); catch != nil {
|
||||||
_ = c.SendStatus(StatusInternalServerError) //nolint:errcheck // It is fine to ignore the error here
|
_ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil
|
||||||
|
}
|
||||||
|
// TODO: Do we need to return here?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) customRequestHandler(rctx *fasthttp.RequestCtx) {
|
||||||
|
// Acquire CustomCtx from the pool
|
||||||
|
ctx, ok := app.AcquireCtx(rctx).(CustomCtx)
|
||||||
|
if !ok {
|
||||||
|
panic(errors.New("requestHandler: failed to type-assert to CustomCtx"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer app.ReleaseCtx(ctx)
|
||||||
|
|
||||||
|
// Check if the HTTP method is valid
|
||||||
|
if app.methodInt(ctx.Method()) == -1 {
|
||||||
|
_ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Check flash messages
|
||||||
|
rawHeaders := ctx.Request().Header.RawHeaders()
|
||||||
|
if len(rawHeaders) > 0 && bytes.Contains(rawHeaders, []byte(FlashCookieName)) {
|
||||||
|
ctx.Redirect().parseAndClearFlashMessages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to match a route and execute the chain
|
||||||
|
_, err := app.nextCustom(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if catch := ctx.App().ErrorHandler(ctx, err); catch != nil {
|
||||||
|
_ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil
|
||||||
}
|
}
|
||||||
// TODO: Do we need to return here?
|
// TODO: Do we need to return here?
|
||||||
}
|
}
|
||||||
|
@ -295,68 +324,56 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
|
||||||
handlers = append(handlers, handler)
|
handlers = append(handlers, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range methods {
|
// Precompute path normalization ONCE
|
||||||
// Uppercase HTTP methods
|
|
||||||
method = utils.ToUpper(method)
|
|
||||||
// Check if the HTTP method is valid unless it's USE
|
|
||||||
if method != methodUse && app.methodInt(method) == -1 {
|
|
||||||
panic(fmt.Sprintf("add: invalid http method %s\n", method))
|
|
||||||
}
|
|
||||||
// is mounted app
|
|
||||||
isMount := group != nil && group.app != app
|
|
||||||
// A route requires atleast one ctx handler
|
|
||||||
if len(handlers) == 0 && !isMount {
|
|
||||||
panic(fmt.Sprintf("missing handler/middleware in route: %s\n", pathRaw))
|
|
||||||
}
|
|
||||||
// Cannot have an empty path
|
|
||||||
if pathRaw == "" {
|
if pathRaw == "" {
|
||||||
pathRaw = "/"
|
pathRaw = "/"
|
||||||
}
|
}
|
||||||
// Path always start with a '/'
|
|
||||||
if pathRaw[0] != '/' {
|
if pathRaw[0] != '/' {
|
||||||
pathRaw = "/" + pathRaw
|
pathRaw = "/" + pathRaw
|
||||||
}
|
}
|
||||||
// Create a stripped path in case-sensitive / trailing slashes
|
|
||||||
pathPretty := pathRaw
|
pathPretty := pathRaw
|
||||||
// Case-sensitive routing, all to lowercase
|
|
||||||
if !app.config.CaseSensitive {
|
if !app.config.CaseSensitive {
|
||||||
pathPretty = utils.ToLower(pathPretty)
|
pathPretty = utils.ToLower(pathPretty)
|
||||||
}
|
}
|
||||||
// Strict routing, remove trailing slashes
|
|
||||||
if !app.config.StrictRouting && len(pathPretty) > 1 {
|
if !app.config.StrictRouting && len(pathPretty) > 1 {
|
||||||
pathPretty = utils.TrimRight(pathPretty, '/')
|
pathPretty = utils.TrimRight(pathPretty, '/')
|
||||||
}
|
}
|
||||||
// Is layer a middleware?
|
pathClean := RemoveEscapeChar(pathPretty)
|
||||||
isUse := method == methodUse
|
|
||||||
// Is path a direct wildcard?
|
|
||||||
isStar := pathPretty == "/*"
|
|
||||||
// Is path a root slash?
|
|
||||||
isRoot := pathPretty == "/"
|
|
||||||
// Parse path parameters
|
|
||||||
parsedRaw := parseRoute(pathRaw, app.customConstraints...)
|
parsedRaw := parseRoute(pathRaw, app.customConstraints...)
|
||||||
parsedPretty := parseRoute(pathPretty, app.customConstraints...)
|
parsedPretty := parseRoute(pathPretty, app.customConstraints...)
|
||||||
|
|
||||||
// Create route metadata without pointer
|
for _, method := range methods {
|
||||||
|
method = utils.ToUpper(method)
|
||||||
|
if method != methodUse && app.methodInt(method) == -1 {
|
||||||
|
panic(fmt.Sprintf("add: invalid http method %s\n", method))
|
||||||
|
}
|
||||||
|
|
||||||
|
isMount := group != nil && group.app != app
|
||||||
|
if len(handlers) == 0 && !isMount {
|
||||||
|
panic(fmt.Sprintf("missing handler/middleware in route: %s\n", pathRaw))
|
||||||
|
}
|
||||||
|
|
||||||
|
isUse := method == methodUse
|
||||||
|
isStar := pathClean == "/*"
|
||||||
|
isRoot := pathClean == "/"
|
||||||
|
|
||||||
route := Route{
|
route := Route{
|
||||||
// Router booleans
|
|
||||||
use: isUse,
|
use: isUse,
|
||||||
mount: isMount,
|
mount: isMount,
|
||||||
star: isStar,
|
star: isStar,
|
||||||
root: isRoot,
|
root: isRoot,
|
||||||
|
|
||||||
// Path data
|
path: pathClean,
|
||||||
path: RemoveEscapeChar(pathPretty),
|
|
||||||
routeParser: parsedPretty,
|
routeParser: parsedPretty,
|
||||||
Params: parsedRaw.params,
|
Params: parsedRaw.params,
|
||||||
|
|
||||||
// Group data
|
|
||||||
group: group,
|
group: group,
|
||||||
|
|
||||||
// Public data
|
|
||||||
Path: pathRaw,
|
Path: pathRaw,
|
||||||
Method: method,
|
Method: method,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment global handler count
|
// Increment global handler count
|
||||||
atomic.AddUint32(&app.handlersCount, uint32(len(handlers))) //nolint:gosec // Not a concern
|
atomic.AddUint32(&app.handlersCount, uint32(len(handlers))) //nolint:gosec // Not a concern
|
||||||
|
|
||||||
|
|
|
@ -591,6 +591,29 @@ func Benchmark_Router_Next_Default(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -benchmem -run=^$ -bench ^Benchmark_Router_Next_Default_Parallel$ github.com/gofiber/fiber/v3 -count=1
|
||||||
|
func Benchmark_Router_Next_Default_Parallel(b *testing.B) {
|
||||||
|
app := New()
|
||||||
|
app.Get("/", func(_ Ctx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
h := app.Handler()
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
fctx := &fasthttp.RequestCtx{}
|
||||||
|
fctx.Request.Header.SetMethod(MethodGet)
|
||||||
|
fctx.Request.SetRequestURI("/")
|
||||||
|
|
||||||
|
for pb.Next() {
|
||||||
|
h(fctx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4
|
// go test -v ./... -run=^$ -bench=Benchmark_Route_Match -benchmem -count=4
|
||||||
func Benchmark_Route_Match(b *testing.B) {
|
func Benchmark_Route_Match(b *testing.B) {
|
||||||
var match bool
|
var match bool
|
||||||
|
|
Loading…
Reference in New Issue