diff --git a/app.go b/app.go index 9570458c..ae16a618 100644 --- a/app.go +++ b/app.go @@ -458,17 +458,29 @@ const ( DefaultWriteBufferSize = 4096 ) +const ( + methodGet = iota + methodHead + methodPost + methodPut + methodDelete + methodConnect + methodOptions + methodTrace + methodPatch +) + // HTTP methods enabled by default var DefaultMethods = []string{ - MethodGet, - MethodHead, - MethodPost, - MethodPut, - MethodDelete, - MethodConnect, - MethodOptions, - MethodTrace, - MethodPatch, + methodGet: MethodGet, + methodHead: MethodHead, + methodPost: MethodPost, + methodPut: MethodPut, + methodDelete: MethodDelete, + methodConnect: MethodConnect, + methodOptions: MethodOptions, + methodTrace: MethodTrace, + methodPatch: MethodPatch, } // DefaultErrorHandler that process return errors from handlers diff --git a/ctx.go b/ctx.go index b959a48f..1816185e 100644 --- a/ctx.go +++ b/ctx.go @@ -61,7 +61,6 @@ type DefaultCtx struct { res *DefaultRes // Default response api reference values [maxParams]string // Route parameter values viewBindMap sync.Map // Default view map to bind template engine - method string // HTTP method baseURI string // HTTP base uri pathOriginal string // Original HTTP path flashMessages redirectionMsgs // Flash messages @@ -70,7 +69,7 @@ type DefaultCtx struct { treePathHash int // Hash of the path for the search in the tree indexRoute int // Index of the current route 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 } @@ -1006,19 +1005,17 @@ func (c *DefaultCtx) Location(path string) { func (c *DefaultCtx) Method(override ...string) string { if len(override) == 0 { // Nothing to override, just return current method from context - return c.method + return c.app.method(c.methodInt) } method := utils.ToUpper(override[0]) - mINT := c.app.methodInt(method) - if mINT == -1 { + methodInt := c.app.methodInt(method) + if methodInt == -1 { // Provided override does not valid HTTP method, no override, return current method - return c.method + return c.app.method(c.methodInt) } - - c.method = method - c.methodINT = mINT - return c.method + c.methodInt = methodInt + return method } // MultipartForm parse form entries from binary. @@ -1486,7 +1483,7 @@ func (c *DefaultCtx) Route() *Route { return &Route{ path: c.pathOriginal, Path: c.pathOriginal, - Method: c.method, + Method: c.Method(), Handlers: make([]Handler, 0), Params: make([]string, 0), } @@ -1919,8 +1916,7 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) { // Set paths c.pathOriginal = c.app.getString(fctx.URI().PathOriginal()) // Set method - c.method = c.app.getString(fctx.Request.Header.Method()) - c.methodINT = c.app.methodInt(c.method) + c.methodInt = c.app.methodInt(utils.UnsafeString(fctx.Request.Header.Method())) // Attach *fasthttp.RequestCtx to ctx c.fasthttp = fctx // reset base uri @@ -1951,8 +1947,8 @@ func (c *DefaultCtx) getBody() []byte { } // Methods to use with next stack. -func (c *DefaultCtx) getMethodINT() int { - return c.methodINT +func (c *DefaultCtx) getMethodInt() int { + return c.methodInt } func (c *DefaultCtx) getIndexRoute() int { diff --git a/ctx_interface.go b/ctx_interface.go index 6ef33847..c2eb8bf4 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -17,7 +17,7 @@ type CustomCtx interface { Reset(fctx *fasthttp.RequestCtx) // Methods to use with next stack. - getMethodINT() int + getMethodInt() int getIndexRoute() int getTreePathHash() int getDetectionPath() string diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index a4d7db3d..df537e26 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -345,7 +345,7 @@ type Ctx interface { release() getBody() []byte // Methods to use with next stack. - getMethodINT() int + getMethodInt() int getIndexRoute() int getTreePathHash() int getDetectionPath() string diff --git a/docs/middleware/healthcheck.md b/docs/middleware/healthcheck.md index 2837c550..122f5768 100644 --- a/docs/middleware/healthcheck.md +++ b/docs/middleware/healthcheck.md @@ -27,7 +27,7 @@ Liveness, readiness and startup probes middleware for [Fiber](https://github.com ## Signatures ```go -func NewHealthChecker(config Config) fiber.Handler +func New(config Config) fiber.Handler ``` ## 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 // 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 -app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) +app.Get(healthcheck.ReadinessEndpoint, healthcheck.New()) + // 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 -app.Get("/live", healthcheck.NewHealthChecker()) +app.Get("/live", healthcheck.New()) // 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 { return true }, })) + // 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 { return true }, })) + // 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 { return true }, })) + // 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 { 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 // 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 -app.All(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker(healthcheck.Config{ +app.All(healthcheck.ReadinessEndpoint, healthcheck.New(healthcheck.Config{ Probe: func(c fiber.Ctx) bool { return true }, @@ -108,7 +114,7 @@ type Config struct { // initialization and readiness checks // // 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: ```go -func defaultProbe(fiber.Ctx) bool { return true } +func defaultProbe(_ fiber.Ctx) bool { return true } var ConfigDefault = Config{ Probe: defaultProbe, diff --git a/docs/whats_new.md b/docs/whats_new.md index ca7dfb42..4bf28927 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -1565,25 +1565,25 @@ With the new version, each health check endpoint is configured separately, allow // after // 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 { return true }, })) // Default readiness endpoint configuration -app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker()) +app.Get(healthcheck.ReadinessEndpoint, healthcheck.New()) // New default startup endpoint configuration // 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 { return serviceA.Ready() && serviceB.Ready() && ... }, })) // Custom liveness endpoint configuration -app.Get("/live", healthcheck.NewHealthChecker()) +app.Get("/live", healthcheck.New()) ``` #### Monitor diff --git a/helpers.go b/helpers.go index 3f1685b1..573aab3d 100644 --- a/helpers.go +++ b/helpers.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "strconv" "strings" "sync" @@ -107,7 +108,7 @@ func (app *App) methodExist(c *DefaultCtx) bool { methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method - if c.getMethodINT() == i { + if c.getMethodInt() == i { continue } // Reset stack index @@ -151,7 +152,7 @@ func (app *App) methodExistCustom(c CustomCtx) bool { methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method - if c.getMethodINT() == i { + if c.getMethodInt() == i { continue } // Reset stack index @@ -652,39 +653,35 @@ func getBytesImmutable(s string) []byte { func (app *App) methodInt(s string) int { // For better performance if len(app.configured.RequestMethods) == 0 { - // TODO: Use iota instead switch s { case MethodGet: - return 0 + return methodGet case MethodHead: - return 1 + return methodHead case MethodPost: - return 2 + return methodPost case MethodPut: - return 3 + return methodPut case MethodDelete: - return 4 + return methodDelete case MethodConnect: - return 5 + return methodConnect case MethodOptions: - return 6 + return methodOptions case MethodTrace: - return 7 + return methodTrace case MethodPatch: - return 8 + return methodPatch default: return -1 } } - // For method customization - for i, v := range app.config.RequestMethods { - if s == v { - return i - } - } + return slices.Index(app.config.RequestMethods, s) +} - return -1 +func (app *App) method(methodInt int) string { + return app.config.RequestMethods[methodInt] } // IsMethodSafe reports whether the HTTP method is considered safe. diff --git a/middleware/healthcheck/config.go b/middleware/healthcheck/config.go index eba6d537..112f4039 100644 --- a/middleware/healthcheck/config.go +++ b/middleware/healthcheck/config.go @@ -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). // // Optional. Default: func(c fiber.Ctx) bool { return true } - Probe HealthChecker + Probe func(fiber.Ctx) bool } const ( - DefaultLivenessEndpoint = "/livez" - DefaultReadinessEndpoint = "/readyz" - DefaultStartupEndpoint = "/startupz" + LivenessEndpoint = "/livez" + ReadinessEndpoint = "/readyz" + 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 { return Config{ Probe: defaultProbe, diff --git a/middleware/healthcheck/healthcheck.go b/middleware/healthcheck/healthcheck.go index 51a16d70..de2079ae 100644 --- a/middleware/healthcheck/healthcheck.go +++ b/middleware/healthcheck/healthcheck.go @@ -4,11 +4,8 @@ import ( "github.com/gofiber/fiber/v3" ) -// HealthChecker defines a function to check liveness or readiness of the application -type HealthChecker func(fiber.Ctx) bool - -func NewHealthChecker(config ...Config) fiber.Handler { - cfg := defaultConfigV3(config...) +func New(config ...Config) fiber.Handler { + cfg := defaultConfig(config...) return func(c fiber.Ctx) error { // Don't execute middleware if Next returns true diff --git a/middleware/healthcheck/healthcheck_test.go b/middleware/healthcheck/healthcheck_test.go index 07efa3de..bccfddde 100644 --- a/middleware/healthcheck/healthcheck_test.go +++ b/middleware/healthcheck/healthcheck_test.go @@ -34,9 +34,9 @@ func Test_HealthCheck_Strict_Routing_Default(t *testing.T) { StrictRouting: true, }) - app.Get(DefaultLivenessEndpoint, NewHealthChecker()) - app.Get(DefaultReadinessEndpoint, NewHealthChecker()) - app.Get(DefaultStartupEndpoint, NewHealthChecker()) + app.Get(LivenessEndpoint, New()) + app.Get(ReadinessEndpoint, New()) + app.Get(StartupEndpoint, New()) shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/livez") @@ -53,9 +53,9 @@ func Test_HealthCheck_Default(t *testing.T) { t.Parallel() app := fiber.New() - app.Get(DefaultLivenessEndpoint, NewHealthChecker()) - app.Get(DefaultReadinessEndpoint, NewHealthChecker()) - app.Get(DefaultStartupEndpoint, NewHealthChecker()) + app.Get(LivenessEndpoint, New()) + app.Get(ReadinessEndpoint, New()) + app.Get(StartupEndpoint, New()) shouldGiveOK(t, app, "/readyz") shouldGiveOK(t, app, "/livez") @@ -73,12 +73,12 @@ func Test_HealthCheck_Custom(t *testing.T) { app := fiber.New() c1 := make(chan struct{}, 1) - app.Get("/live", NewHealthChecker(Config{ + app.Get("/live", New(Config{ Probe: func(_ fiber.Ctx) bool { return true }, })) - app.Get("/ready", NewHealthChecker(Config{ + app.Get("/ready", New(Config{ Probe: func(_ fiber.Ctx) bool { select { 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 { return false }, @@ -123,12 +123,12 @@ func Test_HealthCheck_Custom_Nested(t *testing.T) { app := fiber.New() c1 := make(chan struct{}, 1) - app.Get("/probe/live", NewHealthChecker(Config{ + app.Get("/probe/live", New(Config{ Probe: func(_ fiber.Ctx) bool { return true }, })) - app.Get("/probe/ready", NewHealthChecker(Config{ + app.Get("/probe/ready", New(Config{ Probe: func(_ fiber.Ctx) bool { select { case <-c1: @@ -164,15 +164,15 @@ func Test_HealthCheck_Next(t *testing.T) { app := fiber.New() - checker := NewHealthChecker(Config{ + checker := New(Config{ Next: func(_ fiber.Ctx) bool { return true }, }) - app.Get(DefaultLivenessEndpoint, checker) - app.Get(DefaultReadinessEndpoint, checker) - app.Get(DefaultStartupEndpoint, checker) + app.Get(LivenessEndpoint, checker) + app.Get(ReadinessEndpoint, checker) + app.Get(StartupEndpoint, checker) // This should give not found since there are no other handlers to execute // 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) { app := fiber.New() - app.Get(DefaultLivenessEndpoint, NewHealthChecker()) - app.Get(DefaultReadinessEndpoint, NewHealthChecker()) - app.Get(DefaultStartupEndpoint, NewHealthChecker()) + app.Get(LivenessEndpoint, New()) + app.Get(ReadinessEndpoint, New()) + app.Get(StartupEndpoint, New()) h := app.Handler() fctx := &fasthttp.RequestCtx{} @@ -206,9 +206,9 @@ func Benchmark_HealthCheck(b *testing.B) { func Benchmark_HealthCheck_Parallel(b *testing.B) { app := fiber.New() - app.Get(DefaultLivenessEndpoint, NewHealthChecker()) - app.Get(DefaultReadinessEndpoint, NewHealthChecker()) - app.Get(DefaultStartupEndpoint, NewHealthChecker()) + app.Get(LivenessEndpoint, New()) + app.Get(ReadinessEndpoint, New()) + app.Get(StartupEndpoint, New()) h := app.Handler() diff --git a/router.go b/router.go index 0aec6509..ac5832bc 100644 --- a/router.go +++ b/router.go @@ -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 // Get stack length - tree, ok := app.treeStack[c.getMethodINT()][c.getTreePathHash()] + tree, ok := app.treeStack[c.getMethodInt()][c.getTreePathHash()] if !ok { - tree = app.treeStack[c.getMethodINT()][0] + tree = app.treeStack[c.getMethodInt()][0] } 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) { // Get stack length - tree, ok := app.treeStack[c.methodINT][c.treePathHash] + tree, ok := app.treeStack[c.methodInt][c.treePathHash] if !ok { - tree = app.treeStack[c.methodINT][0] + tree = app.treeStack[c.methodInt][0] } lenTree := len(tree) - 1 @@ -202,7 +202,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) { } // 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 no match, scan stack again if other methods match the request // 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) // Check if the HTTP method is valid - if ctx.methodINT == -1 { + if ctx.methodInt == -1 { _ = ctx.SendStatus(StatusNotImplemented) //nolint:errcheck // Always return nil return } diff --git a/router_test.go b/router_test.go index 0e6019e2..ecc53dbb 100644 --- a/router_test.go +++ b/router_test.go @@ -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 func Benchmark_Route_Match(b *testing.B) { var match bool