Merge branch 'main' into state-management

state-management
Juan Calderon-Perez 2025-03-30 13:53:02 -04:00 committed by GitHub
commit 99d8db951a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 152 additions and 100 deletions

30
app.go
View File

@ -458,17 +458,29 @@ const (
DefaultWriteBufferSize = 4096 DefaultWriteBufferSize = 4096
) )
const (
methodGet = iota
methodHead
methodPost
methodPut
methodDelete
methodConnect
methodOptions
methodTrace
methodPatch
)
// HTTP methods enabled by default // HTTP methods enabled by default
var DefaultMethods = []string{ var DefaultMethods = []string{
MethodGet, methodGet: MethodGet,
MethodHead, methodHead: MethodHead,
MethodPost, methodPost: MethodPost,
MethodPut, methodPut: MethodPut,
MethodDelete, methodDelete: MethodDelete,
MethodConnect, methodConnect: MethodConnect,
MethodOptions, methodOptions: MethodOptions,
MethodTrace, methodTrace: MethodTrace,
MethodPatch, methodPatch: MethodPatch,
} }
// DefaultErrorHandler that process return errors from handlers // DefaultErrorHandler that process return errors from handlers

26
ctx.go
View File

@ -61,7 +61,6 @@ type DefaultCtx struct {
res *DefaultRes // Default response api reference res *DefaultRes // Default response api reference
values [maxParams]string // Route parameter values values [maxParams]string // Route parameter values
viewBindMap sync.Map // Default view map to bind template engine viewBindMap sync.Map // Default view map to bind template engine
method string // HTTP method
baseURI string // HTTP base uri baseURI string // HTTP base uri
pathOriginal string // Original HTTP path pathOriginal string // Original HTTP path
flashMessages redirectionMsgs // Flash messages flashMessages redirectionMsgs // Flash messages
@ -70,7 +69,7 @@ type DefaultCtx struct {
treePathHash int // Hash of the path for the search in the tree treePathHash int // Hash of the path for the search in the tree
indexRoute int // Index of the current route indexRoute int // Index of the current route
indexHandler int // Index of the current handler indexHandler int // Index of the current handler
methodINT int // HTTP method INT equivalent methodInt int // HTTP method INT equivalent
matched bool // Non use route matched matched bool // Non use route matched
} }
@ -1006,19 +1005,17 @@ func (c *DefaultCtx) Location(path string) {
func (c *DefaultCtx) Method(override ...string) string { func (c *DefaultCtx) Method(override ...string) string {
if len(override) == 0 { if len(override) == 0 {
// Nothing to override, just return current method from context // Nothing to override, just return current method from context
return c.method return c.app.method(c.methodInt)
} }
method := utils.ToUpper(override[0]) method := utils.ToUpper(override[0])
mINT := c.app.methodInt(method) methodInt := c.app.methodInt(method)
if mINT == -1 { if methodInt == -1 {
// Provided override does not valid HTTP method, no override, return current method // Provided override does not valid HTTP method, no override, return current method
return c.method return c.app.method(c.methodInt)
} }
c.methodInt = methodInt
c.method = method return method
c.methodINT = mINT
return c.method
} }
// MultipartForm parse form entries from binary. // MultipartForm parse form entries from binary.
@ -1486,7 +1483,7 @@ func (c *DefaultCtx) Route() *Route {
return &Route{ return &Route{
path: c.pathOriginal, path: c.pathOriginal,
Path: c.pathOriginal, Path: c.pathOriginal,
Method: c.method, Method: c.Method(),
Handlers: make([]Handler, 0), Handlers: make([]Handler, 0),
Params: make([]string, 0), Params: make([]string, 0),
} }
@ -1919,8 +1916,7 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) {
// Set paths // Set paths
c.pathOriginal = c.app.getString(fctx.URI().PathOriginal()) c.pathOriginal = c.app.getString(fctx.URI().PathOriginal())
// Set method // Set method
c.method = c.app.getString(fctx.Request.Header.Method()) c.methodInt = c.app.methodInt(utils.UnsafeString(fctx.Request.Header.Method()))
c.methodINT = c.app.methodInt(c.method)
// Attach *fasthttp.RequestCtx to ctx // Attach *fasthttp.RequestCtx to ctx
c.fasthttp = fctx c.fasthttp = fctx
// reset base uri // reset base uri
@ -1951,8 +1947,8 @@ func (c *DefaultCtx) getBody() []byte {
} }
// Methods to use with next stack. // Methods to use with next stack.
func (c *DefaultCtx) getMethodINT() int { func (c *DefaultCtx) getMethodInt() int {
return c.methodINT return c.methodInt
} }
func (c *DefaultCtx) getIndexRoute() int { func (c *DefaultCtx) getIndexRoute() int {

View File

@ -17,7 +17,7 @@ type CustomCtx interface {
Reset(fctx *fasthttp.RequestCtx) Reset(fctx *fasthttp.RequestCtx)
// Methods to use with next stack. // Methods to use with next stack.
getMethodINT() int getMethodInt() int
getIndexRoute() int getIndexRoute() int
getTreePathHash() int getTreePathHash() int
getDetectionPath() string getDetectionPath() string

View File

@ -345,7 +345,7 @@ type Ctx interface {
release() release()
getBody() []byte getBody() []byte
// Methods to use with next stack. // Methods to use with next stack.
getMethodINT() int getMethodInt() int
getIndexRoute() int getIndexRoute() int
getTreePathHash() int getTreePathHash() int
getDetectionPath() string getDetectionPath() string

View File

@ -27,7 +27,7 @@ Liveness, readiness and startup probes middleware for [Fiber](https://github.com
## Signatures ## Signatures
```go ```go
func NewHealthChecker(config Config) fiber.Handler func New(config Config) fiber.Handler
``` ```
## Examples ## Examples
@ -41,38 +41,44 @@ import(
) )
``` ```
After you initiate your [Fiber](https://github.com/gofiber/fiber) app, you can use the following possibilities: After you initiate your [Fiber](https://github.com/gofiber/fiber) app, you can use the following options:
```go ```go
// Provide a minimal config for liveness check // Provide a minimal config for liveness check
app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker()) app.Get(healthcheck.LivenessEndpoint, healthcheck.New())
// Provide a minimal config for readiness check // Provide a minimal config for readiness check
app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) app.Get(healthcheck.ReadinessEndpoint, healthcheck.New())
// Provide a minimal config for startup check // Provide a minimal config for startup check
app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker()) app.Get(healthcheck.StartupEndpoint, healthcheck.New())
// Provide a minimal config for check with custom endpoint // Provide a minimal config for check with custom endpoint
app.Get("/live", healthcheck.NewHealthChecker()) app.Get("/live", healthcheck.New())
// Or extend your config for customization // Or extend your config for customization
app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
})) }))
// And it works the same for readiness, just change the route // And it works the same for readiness, just change the route
app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.Get(healthcheck.ReadinessEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
})) }))
// And it works the same for startup, just change the route // And it works the same for startup, just change the route
app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.Get(healthcheck.StartupEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
})) }))
// With a custom route and custom probe // With a custom route and custom probe
app.Get("/live", healthcheck.NewHealthChecker(healthcheck.Config{ app.Get("/live", healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
@ -81,7 +87,7 @@ app.Get("/live", healthcheck.NewHealthChecker(healthcheck.Config{
// It can also be used with app.All, although it will only respond to requests with the GET method // It can also be used with app.All, although it will only respond to requests with the GET method
// in case of calling the route with any method which isn't GET, the return will be 404 Not Found when app.All is used // in case of calling the route with any method which isn't GET, the return will be 404 Not Found when app.All is used
// and 405 Method Not Allowed when app.Get is used // and 405 Method Not Allowed when app.Get is used
app.All(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.All(healthcheck.ReadinessEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
@ -108,7 +114,7 @@ type Config struct {
// initialization and readiness checks // initialization and readiness checks
// //
// Optional. Default: func(c fiber.Ctx) bool { return true } // Optional. Default: func(c fiber.Ctx) bool { return true }
Probe HealthChecker Probe func(fiber.Ctx) bool
} }
``` ```
@ -117,7 +123,7 @@ type Config struct {
The default configuration used by this middleware is defined as follows: The default configuration used by this middleware is defined as follows:
```go ```go
func defaultProbe(fiber.Ctx) bool { return true } func defaultProbe(_ fiber.Ctx) bool { return true }
var ConfigDefault = Config{ var ConfigDefault = Config{
Probe: defaultProbe, Probe: defaultProbe,

View File

@ -1565,25 +1565,25 @@ With the new version, each health check endpoint is configured separately, allow
// after // after
// Default liveness endpoint configuration // Default liveness endpoint configuration
app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return true return true
}, },
})) }))
// Default readiness endpoint configuration // Default readiness endpoint configuration
app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) app.Get(healthcheck.ReadinessEndpoint, healthcheck.New())
// New default startup endpoint configuration // New default startup endpoint configuration
// Default endpoint is /startupz // Default endpoint is /startupz
app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ app.Get(healthcheck.StartupEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool { Probe: func(c fiber.Ctx) bool {
return serviceA.Ready() && serviceB.Ready() && ... return serviceA.Ready() && serviceB.Ready() && ...
}, },
})) }))
// Custom liveness endpoint configuration // Custom liveness endpoint configuration
app.Get("/live", healthcheck.NewHealthChecker()) app.Get("/live", healthcheck.New())
``` ```
#### Monitor #### Monitor

View File

@ -14,6 +14,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -107,7 +108,7 @@ func (app *App) methodExist(c *DefaultCtx) bool {
methods := app.config.RequestMethods methods := app.config.RequestMethods
for i := 0; i < len(methods); i++ { for i := 0; i < len(methods); i++ {
// Skip original method // Skip original method
if c.getMethodINT() == i { if c.getMethodInt() == i {
continue continue
} }
// Reset stack index // Reset stack index
@ -151,7 +152,7 @@ func (app *App) methodExistCustom(c CustomCtx) bool {
methods := app.config.RequestMethods methods := app.config.RequestMethods
for i := 0; i < len(methods); i++ { for i := 0; i < len(methods); i++ {
// Skip original method // Skip original method
if c.getMethodINT() == i { if c.getMethodInt() == i {
continue continue
} }
// Reset stack index // Reset stack index
@ -652,39 +653,35 @@ func getBytesImmutable(s string) []byte {
func (app *App) methodInt(s string) int { func (app *App) methodInt(s string) int {
// For better performance // For better performance
if len(app.configured.RequestMethods) == 0 { if len(app.configured.RequestMethods) == 0 {
// TODO: Use iota instead
switch s { switch s {
case MethodGet: case MethodGet:
return 0 return methodGet
case MethodHead: case MethodHead:
return 1 return methodHead
case MethodPost: case MethodPost:
return 2 return methodPost
case MethodPut: case MethodPut:
return 3 return methodPut
case MethodDelete: case MethodDelete:
return 4 return methodDelete
case MethodConnect: case MethodConnect:
return 5 return methodConnect
case MethodOptions: case MethodOptions:
return 6 return methodOptions
case MethodTrace: case MethodTrace:
return 7 return methodTrace
case MethodPatch: case MethodPatch:
return 8 return methodPatch
default: default:
return -1 return -1
} }
} }
// For method customization // For method customization
for i, v := range app.config.RequestMethods { return slices.Index(app.config.RequestMethods, s)
if s == v { }
return i
}
}
return -1 func (app *App) method(methodInt int) string {
return app.config.RequestMethods[methodInt]
} }
// IsMethodSafe reports whether the HTTP method is considered safe. // IsMethodSafe reports whether the HTTP method is considered safe.

View File

@ -18,18 +18,18 @@ type Config struct {
// the application is in a state where it can handle requests (e.g., the server is up and running). // the application is in a state where it can handle requests (e.g., the server is up and running).
// //
// Optional. Default: func(c fiber.Ctx) bool { return true } // Optional. Default: func(c fiber.Ctx) bool { return true }
Probe HealthChecker Probe func(fiber.Ctx) bool
} }
const ( const (
DefaultLivenessEndpoint = "/livez" LivenessEndpoint = "/livez"
DefaultReadinessEndpoint = "/readyz" ReadinessEndpoint = "/readyz"
DefaultStartupEndpoint = "/startupz" StartupEndpoint = "/startupz"
) )
func defaultProbe(fiber.Ctx) bool { return true } func defaultProbe(_ fiber.Ctx) bool { return true }
func defaultConfigV3(config ...Config) Config { func defaultConfig(config ...Config) Config {
if len(config) < 1 { if len(config) < 1 {
return Config{ return Config{
Probe: defaultProbe, Probe: defaultProbe,

View File

@ -4,11 +4,8 @@ import (
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
// HealthChecker defines a function to check liveness or readiness of the application func New(config ...Config) fiber.Handler {
type HealthChecker func(fiber.Ctx) bool cfg := defaultConfig(config...)
func NewHealthChecker(config ...Config) fiber.Handler {
cfg := defaultConfigV3(config...)
return func(c fiber.Ctx) error { return func(c fiber.Ctx) error {
// Don't execute middleware if Next returns true // Don't execute middleware if Next returns true

View File

@ -34,9 +34,9 @@ func Test_HealthCheck_Strict_Routing_Default(t *testing.T) {
StrictRouting: true, StrictRouting: true,
}) })
app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(LivenessEndpoint, New())
app.Get(DefaultReadinessEndpoint, NewHealthChecker()) app.Get(ReadinessEndpoint, New())
app.Get(DefaultStartupEndpoint, NewHealthChecker()) app.Get(StartupEndpoint, New())
shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/readyz")
shouldGiveOK(t, app, "/livez") shouldGiveOK(t, app, "/livez")
@ -53,9 +53,9 @@ func Test_HealthCheck_Default(t *testing.T) {
t.Parallel() t.Parallel()
app := fiber.New() app := fiber.New()
app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(LivenessEndpoint, New())
app.Get(DefaultReadinessEndpoint, NewHealthChecker()) app.Get(ReadinessEndpoint, New())
app.Get(DefaultStartupEndpoint, NewHealthChecker()) app.Get(StartupEndpoint, New())
shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/readyz")
shouldGiveOK(t, app, "/livez") shouldGiveOK(t, app, "/livez")
@ -73,12 +73,12 @@ func Test_HealthCheck_Custom(t *testing.T) {
app := fiber.New() app := fiber.New()
c1 := make(chan struct{}, 1) c1 := make(chan struct{}, 1)
app.Get("/live", NewHealthChecker(Config{ app.Get("/live", New(Config{
Probe: func(_ fiber.Ctx) bool { Probe: func(_ fiber.Ctx) bool {
return true return true
}, },
})) }))
app.Get("/ready", NewHealthChecker(Config{ app.Get("/ready", New(Config{
Probe: func(_ fiber.Ctx) bool { Probe: func(_ fiber.Ctx) bool {
select { select {
case <-c1: case <-c1:
@ -88,7 +88,7 @@ func Test_HealthCheck_Custom(t *testing.T) {
} }
}, },
})) }))
app.Get(DefaultStartupEndpoint, NewHealthChecker(Config{ app.Get(StartupEndpoint, New(Config{
Probe: func(_ fiber.Ctx) bool { Probe: func(_ fiber.Ctx) bool {
return false return false
}, },
@ -123,12 +123,12 @@ func Test_HealthCheck_Custom_Nested(t *testing.T) {
app := fiber.New() app := fiber.New()
c1 := make(chan struct{}, 1) c1 := make(chan struct{}, 1)
app.Get("/probe/live", NewHealthChecker(Config{ app.Get("/probe/live", New(Config{
Probe: func(_ fiber.Ctx) bool { Probe: func(_ fiber.Ctx) bool {
return true return true
}, },
})) }))
app.Get("/probe/ready", NewHealthChecker(Config{ app.Get("/probe/ready", New(Config{
Probe: func(_ fiber.Ctx) bool { Probe: func(_ fiber.Ctx) bool {
select { select {
case <-c1: case <-c1:
@ -164,15 +164,15 @@ func Test_HealthCheck_Next(t *testing.T) {
app := fiber.New() app := fiber.New()
checker := NewHealthChecker(Config{ checker := New(Config{
Next: func(_ fiber.Ctx) bool { Next: func(_ fiber.Ctx) bool {
return true return true
}, },
}) })
app.Get(DefaultLivenessEndpoint, checker) app.Get(LivenessEndpoint, checker)
app.Get(DefaultReadinessEndpoint, checker) app.Get(ReadinessEndpoint, checker)
app.Get(DefaultStartupEndpoint, checker) app.Get(StartupEndpoint, checker)
// This should give not found since there are no other handlers to execute // This should give not found since there are no other handlers to execute
// so it's like the route isn't defined at all // so it's like the route isn't defined at all
@ -184,9 +184,9 @@ func Test_HealthCheck_Next(t *testing.T) {
func Benchmark_HealthCheck(b *testing.B) { func Benchmark_HealthCheck(b *testing.B) {
app := fiber.New() app := fiber.New()
app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(LivenessEndpoint, New())
app.Get(DefaultReadinessEndpoint, NewHealthChecker()) app.Get(ReadinessEndpoint, New())
app.Get(DefaultStartupEndpoint, NewHealthChecker()) app.Get(StartupEndpoint, New())
h := app.Handler() h := app.Handler()
fctx := &fasthttp.RequestCtx{} fctx := &fasthttp.RequestCtx{}
@ -206,9 +206,9 @@ func Benchmark_HealthCheck(b *testing.B) {
func Benchmark_HealthCheck_Parallel(b *testing.B) { func Benchmark_HealthCheck_Parallel(b *testing.B) {
app := fiber.New() app := fiber.New()
app.Get(DefaultLivenessEndpoint, NewHealthChecker()) app.Get(LivenessEndpoint, New())
app.Get(DefaultReadinessEndpoint, NewHealthChecker()) app.Get(ReadinessEndpoint, New())
app.Get(DefaultStartupEndpoint, NewHealthChecker()) app.Get(StartupEndpoint, New())
h := app.Handler() h := app.Handler()

View File

@ -110,9 +110,9 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo
func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool param might be useful for testing
// Get stack length // Get stack length
tree, ok := app.treeStack[c.getMethodINT()][c.getTreePathHash()] tree, ok := app.treeStack[c.getMethodInt()][c.getTreePathHash()]
if !ok { if !ok {
tree = app.treeStack[c.getMethodINT()][0] tree = app.treeStack[c.getMethodInt()][0]
} }
lenr := len(tree) - 1 lenr := len(tree) - 1
@ -158,9 +158,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool
func (app *App) next(c *DefaultCtx) (bool, error) { func (app *App) next(c *DefaultCtx) (bool, error) {
// Get stack length // Get stack length
tree, ok := app.treeStack[c.methodINT][c.treePathHash] tree, ok := app.treeStack[c.methodInt][c.treePathHash]
if !ok { if !ok {
tree = app.treeStack[c.methodINT][0] tree = app.treeStack[c.methodInt][0]
} }
lenTree := len(tree) - 1 lenTree := len(tree) - 1
@ -202,7 +202,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) {
} }
// If c.Next() does not match, return 404 // If c.Next() does not match, return 404
err := NewError(StatusNotFound, "Cannot "+c.method+" "+html.EscapeString(c.pathOriginal)) err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+html.EscapeString(c.pathOriginal))
if !c.matched && app.methodExist(c) { if !c.matched && app.methodExist(c) {
// If no match, scan stack again if other methods match the request // If no match, scan stack again if other methods match the request
// Moved from app.handler because middleware may break the route chain // Moved from app.handler because middleware may break the route chain
@ -221,7 +221,7 @@ func (app *App) defaultRequestHandler(rctx *fasthttp.RequestCtx) {
defer app.ReleaseCtx(ctx) defer app.ReleaseCtx(ctx)
// Check if the HTTP method is valid // Check if the HTTP method is valid
if ctx.methodINT == -1 { if ctx.methodInt == -1 {
_ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil _ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil
return return
} }

View File

@ -656,6 +656,50 @@ func Benchmark_Router_Next_Default_Parallel(b *testing.B) {
}) })
} }
// go test -v ./... -run=^$ -bench=Benchmark_Router_Next_Default_Immutable -benchmem -count=4
func Benchmark_Router_Next_Default_Immutable(b *testing.B) {
app := New(Config{Immutable: true})
app.Get("/", func(_ Ctx) error {
return nil
})
h := app.Handler()
fctx := &fasthttp.RequestCtx{}
fctx.Request.Header.SetMethod(MethodGet)
fctx.Request.SetRequestURI("/")
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
h(fctx)
}
}
// go test -benchmem -run=^$ -bench ^Benchmark_Router_Next_Default_Parallel_Immutable$ github.com/gofiber/fiber/v3 -count=1
func Benchmark_Router_Next_Default_Parallel_Immutable(b *testing.B) {
app := New(Config{Immutable: true})
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