diff --git a/app.go b/app.go index ec55f06a49..a9c331bd00 100644 --- a/app.go +++ b/app.go @@ -35,7 +35,7 @@ import ( const Version = "3.0.0-beta.4" // Handler defines a function to serve HTTP requests. -type Handler = func(Ctx) error +type Handler[TCtx CtxGeneric[TCtx]] = func(ctx TCtx) error // Map is a shortcut for map[string]any, useful for JSON returns type Map map[string]any @@ -78,7 +78,7 @@ type Storage interface { // return c.Status(code).SendString(err.Error()) // } // app := fiber.New(cfg) -type ErrorHandler = func(Ctx, error) error +type ErrorHandler[TCtx CtxGeneric[TCtx]] = func(TCtx, error) error // Error represents an error that occurred while handling a request. type Error struct { @@ -87,7 +87,7 @@ type Error struct { } // App denotes the Fiber application. -type App struct { +type App[TCtx CtxGeneric[TCtx]] struct { // Ctx pool pool sync.Pool // Fasthttp server @@ -97,19 +97,19 @@ type App struct { // Converts byte slice to a string getString func(b []byte) string // Hooks - hooks *Hooks + hooks *Hooks[TCtx] // Latest route & group - latestRoute *Route + latestRoute *Route[TCtx] // newCtxFunc - newCtxFunc func(app *App) CustomCtx + newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx] // TLS handler tlsHandler *TLSHandler // Mount fields - mountFields *mountFields + mountFields *mountFields[TCtx] // Route stack divided by HTTP methods - stack [][]*Route + stack [][]*Route[TCtx] // Route stack divided by HTTP methods and route prefixes - treeStack []map[string][]*Route + treeStack []map[string][]*Route[TCtx] // custom binders customBinders []CustomBinder // customConstraints is a list of external constraints @@ -117,9 +117,9 @@ type App struct { // sendfiles stores configurations for handling ctx.SendFile operations sendfiles []*sendFileStore // App config - config Config + config Config[TCtx] // Indicates if the value was explicitly configured - configured Config + configured Config[TCtx] // sendfilesMutex is a mutex used for sendfile operations sendfilesMutex sync.RWMutex mutex sync.Mutex @@ -132,7 +132,7 @@ type App struct { } // Config is a struct holding the server settings. -type Config struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore +type Config[TCtx CtxGeneric[TCtx]] struct { //nolint:govet // Aligning the struct fields is not necessary. betteralign:ignore // Enables the "Server: value" HTTP header. // // Default: "" @@ -250,7 +250,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // ErrorHandler is executed when an error is returned from fiber.Handler. // // Default: DefaultErrorHandler - ErrorHandler ErrorHandler `json:"-"` + ErrorHandler ErrorHandler[TCtx] `json:"-"` // When set to true, disables keep-alive connections. // The server will close incoming connections after sending the first response to client. @@ -470,7 +470,7 @@ var DefaultMethods = []string{ } // DefaultErrorHandler that process return errors from handlers -func DefaultErrorHandler(c Ctx, err error) error { +func DefaultErrorHandler[TCtx CtxGeneric[TCtx]](c TCtx, err error) error { code := StatusInternalServerError var e *Error if errors.As(err, &e) { @@ -490,14 +490,54 @@ func DefaultErrorHandler(c Ctx, err error) error { // Prefork: true, // ServerHeader: "Fiber", // }) -func New(config ...Config) *App { +func New(config ...Config[*DefaultCtx]) *App[*DefaultCtx] { + app := newApp[*DefaultCtx](config...) + + // Init app + app.init() + + return app +} + +// NewWithCustomCtx creates a new Fiber named instance with a custom context. +// +// app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx[MyCustomCtx] { +// return &MyCustomCtx{ +// DefaultCtx: *fiber.NewDefaultCtx(app), +// } +// }) +// +// You can pass optional configuration options by passing a Config struct: +// +// app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx[MyCustomCtx] { +// return &MyCustomCtx{ +// DefaultCtx: *fiber.NewDefaultCtx(app), +// } +// }, fiber.Config{ +// Prefork: true, +// ServerHeader: "Fiber", +// }) +func NewWithCustomCtx[TCtx CtxGeneric[TCtx]](newCtxFunc func(app *App[TCtx]) CustomCtx[TCtx], config ...Config[TCtx]) *App[TCtx] { + app := newApp[TCtx](config...) + + // Set newCtxFunc + app.newCtxFunc = newCtxFunc + + // Init app + app.init() + + return app +} + +// newApp creates a new Fiber named instance. +func newApp[TCtx CtxGeneric[TCtx]](config ...Config[TCtx]) *App[TCtx] { // Create a new app - app := &App{ + app := &App[TCtx]{ // Create config - config: Config{}, + config: Config[TCtx]{}, getBytes: utils.UnsafeBytes, getString: utils.UnsafeString, - latestRoute: &Route{}, + latestRoute: &Route[TCtx]{}, customBinders: []CustomBinder{}, sendfiles: []*sendFileStore{}, } @@ -510,7 +550,7 @@ func New(config ...Config) *App { } // Define hooks - app.hooks = newHooks(app) + app.hooks = newHooks[TCtx](app) // Define mountFields app.mountFields = newMountFields(app) @@ -549,7 +589,7 @@ func New(config ...Config) *App { } if app.config.ErrorHandler == nil { - app.config.ErrorHandler = DefaultErrorHandler + app.config.ErrorHandler = DefaultErrorHandler[TCtx] } if app.config.JSONEncoder == nil { @@ -580,21 +620,18 @@ func New(config ...Config) *App { } // Create router stack - app.stack = make([][]*Route, len(app.config.RequestMethods)) - app.treeStack = make([]map[string][]*Route, len(app.config.RequestMethods)) + app.stack = make([][]*Route[TCtx], len(app.config.RequestMethods)) + app.treeStack = make([]map[string][]*Route[TCtx], len(app.config.RequestMethods)) // Override colors app.config.ColorScheme = defaultColors(app.config.ColorScheme) - // Init app - app.init() - // Return app return app } // Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not -func (app *App) handleTrustedProxy(ipAddress string) { +func (app *App[TCtx]) handleTrustedProxy(ipAddress string) { if strings.Contains(ipAddress, "/") { _, ipNet, err := net.ParseCIDR(ipAddress) if err != nil { @@ -612,29 +649,19 @@ func (app *App) handleTrustedProxy(ipAddress string) { } } -// NewCtxFunc allows to customize ctx methods as we want. -// Note: It doesn't allow adding new methods, only customizing exist methods. -func (app *App) NewCtxFunc(function func(app *App) CustomCtx) { - app.newCtxFunc = function - - if app.server != nil { - app.server.Handler = app.customRequestHandler - } -} - // RegisterCustomConstraint allows to register custom constraint. -func (app *App) RegisterCustomConstraint(constraint CustomConstraint) { +func (app *App[TCtx]) RegisterCustomConstraint(constraint CustomConstraint) { app.customConstraints = append(app.customConstraints, constraint) } // RegisterCustomBinder Allows to register custom binders to use as Bind().Custom("name"). // They should be compatible with CustomBinder interface. -func (app *App) RegisterCustomBinder(binder CustomBinder) { +func (app *App[TCtx]) RegisterCustomBinder(binder CustomBinder) { app.customBinders = append(app.customBinders, binder) } // SetTLSHandler Can be used to set ClientHelloInfo when using TLS with Listener. -func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { +func (app *App[TCtx]) SetTLSHandler(tlsHandler *TLSHandler) { // Attach the tlsHandler to the config app.mutex.Lock() app.tlsHandler = tlsHandler @@ -642,7 +669,7 @@ func (app *App) SetTLSHandler(tlsHandler *TLSHandler) { } // Name Assign name to specific route. -func (app *App) Name(name string) Router { +func (app *App[TCtx]) Name(name string) Router[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -668,7 +695,7 @@ func (app *App) Name(name string) Router { } // GetRoute Get route by name -func (app *App) GetRoute(name string) Route { +func (app *App[TCtx]) GetRoute(name string) Route[TCtx] { for _, routes := range app.stack { for _, route := range routes { if route.Name == name { @@ -677,12 +704,12 @@ func (app *App) GetRoute(name string) Route { } } - return Route{} + return Route[TCtx]{} } // GetRoutes Get all routes. When filterUseOption equal to true, it will filter the routes registered by the middleware. -func (app *App) GetRoutes(filterUseOption ...bool) []Route { - var rs []Route +func (app *App[TCtx]) GetRoutes(filterUseOption ...bool) []Route[TCtx] { + var rs []Route[TCtx] var filterUse bool if len(filterUseOption) != 0 { filterUse = filterUseOption[0] @@ -719,21 +746,21 @@ func (app *App) GetRoutes(filterUseOption ...bool) []Route { // app.Use("/mounted-path", subApp) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (app *App) Use(args ...any) Router { +func (app *App[TCtx]) Use(args ...any) Router[TCtx] { var prefix string - var subApp *App + var subApp *App[TCtx] var prefixes []string - var handlers []Handler + var handlers []Handler[TCtx] for i := 0; i < len(args); i++ { switch arg := args[i].(type) { case string: prefix = arg - case *App: + case *App[TCtx]: subApp = arg case []string: prefixes = arg - case Handler: + case Handler[TCtx]: handlers = append(handlers, arg) default: panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) @@ -758,66 +785,66 @@ func (app *App) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (app *App) Get(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Get(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodGet}, path, handler, handlers...) } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (app *App) Head(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Head(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodHead}, path, handler, handlers...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (app *App) Post(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Post(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPost}, path, handler, handlers...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (app *App) Put(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Put(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPut}, path, handler, handlers...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (app *App) Delete(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Delete(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodDelete}, path, handler, handlers...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (app *App) Connect(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Connect(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodConnect}, path, handler, handlers...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (app *App) Options(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Options(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodOptions}, path, handler, handlers...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the path to the target resource. -func (app *App) Trace(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Trace(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodTrace}, path, handler, handlers...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (app *App) Patch(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Patch(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add([]string{MethodPatch}, path, handler, handlers...) } // Add allows you to specify multiple HTTP methods to register a route. -func (app *App) Add(methods []string, path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) Add(methods []string, path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { app.register(methods, path, nil, append([]Handler{handler}, handlers...)...) return app } // All will register the handler on all HTTP methods -func (app *App) All(path string, handler Handler, handlers ...Handler) Router { +func (app *App[TCtx]) All(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return app.Add(app.config.RequestMethods, path, handler, handlers...) } @@ -825,8 +852,8 @@ func (app *App) All(path string, handler Handler, handlers ...Handler) Router { // // api := app.Group("/api") // api.Get("/users", handler) -func (app *App) Group(prefix string, handlers ...Handler) Router { - grp := &Group{Prefix: prefix, app: app} +func (app *App[TCtx]) Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] { + grp := &Group[TCtx]{Prefix: prefix, app: app} if len(handlers) > 0 { app.register([]string{methodUse}, prefix, grp, handlers...) } @@ -839,9 +866,9 @@ func (app *App) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (app *App) Route(path string) Register { +func (app *App[TCtx]) Route(path string) Register[TCtx] { // Create new route - route := &Registering{app: app, path: path} + route := &Registering[TCtx]{app: app, path: path} return route } @@ -864,28 +891,25 @@ func NewError(code int, message ...string) *Error { } // Config returns the app config as value ( read-only ). -func (app *App) Config() Config { +func (app *App[TCtx]) Config() Config[TCtx] { return app.config } // Handler returns the server handler. -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[TCtx]) 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 app.startupProcess() - if app.newCtxFunc != nil { - return app.customRequestHandler - } - return app.defaultRequestHandler + return app.requestHandler } // Stack returns the raw router stack. -func (app *App) Stack() [][]*Route { +func (app *App[TCtx]) Stack() [][]*Route[TCtx] { return app.stack } // HandlersCount returns the amount of registered handlers. -func (app *App) HandlersCount() uint32 { +func (app *App[TCtx]) HandlersCount() uint32 { return app.handlersCount } @@ -902,7 +926,7 @@ func (app *App) HandlersCount() uint32 { // app.Shutdown() // // Shutdown does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) Shutdown() error { +func (app *App[TCtx]) Shutdown() error { return app.ShutdownWithContext(context.Background()) } @@ -913,7 +937,7 @@ func (app *App) Shutdown() error { // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. // // ShutdownWithTimeout does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) ShutdownWithTimeout(timeout time.Duration) error { +func (app *App[TCtx]) ShutdownWithTimeout(timeout time.Duration) error { ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) defer cancelFunc() return app.ShutdownWithContext(ctx) @@ -924,7 +948,7 @@ func (app *App) ShutdownWithTimeout(timeout time.Duration) error { // Make sure the program doesn't exit and waits instead for ShutdownWithTimeout to return. // // ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout to something else than 0. -func (app *App) ShutdownWithContext(ctx context.Context) error { +func (app *App[TCtx]) ShutdownWithContext(ctx context.Context) error { app.mutex.Lock() defer app.mutex.Unlock() @@ -943,12 +967,12 @@ func (app *App) ShutdownWithContext(ctx context.Context) error { } // Server returns the underlying fasthttp server -func (app *App) Server() *fasthttp.Server { +func (app *App[TCtx]) Server() *fasthttp.Server { return app.server } // Hooks returns the hook struct to register hooks. -func (app *App) Hooks() *Hooks { +func (app *App[TCtx]) Hooks() *Hooks[TCtx] { return app.hooks } @@ -971,7 +995,7 @@ type TestConfig struct { // Test is used for internal debugging by passing a *http.Request. // Config is optional and defaults to a 1s error on timeout, // 0 timeout will disable it completely. -func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) { +func (app *App[TCtx]) Test(req *http.Request, config ...TestConfig) (*http.Response, error) { // Default config cfg := TestConfig{ Timeout: time.Second, @@ -1059,7 +1083,7 @@ type disableLogger struct{} func (*disableLogger) Printf(string, ...any) { } -func (app *App) init() *App { +func (app *App[TCtx]) init() *App[TCtx] { // lock application app.mutex.Lock() @@ -1078,11 +1102,7 @@ func (app *App) init() *App { } // fasthttp server settings - if app.newCtxFunc != nil { - app.server.Handler = app.customRequestHandler - } else { - app.server.Handler = app.defaultRequestHandler - } + app.server.Handler = app.requestHandler app.server.Name = app.config.ServerHeader app.server.Concurrency = app.config.Concurrency app.server.NoDefaultDate = app.config.DisableDefaultDate @@ -1111,9 +1131,9 @@ func (app *App) init() *App { // sub fibers by their prefixes and if it finds a match, it uses that // error handler. Otherwise it uses the configured error handler for // the app, which if not set is the DefaultErrorHandler. -func (app *App) ErrorHandler(ctx Ctx, err error) error { +func (app *App[TCtx]) ErrorHandler(ctx TCtx, err error) error { var ( - mountedErrHandler ErrorHandler + mountedErrHandler ErrorHandler[TCtx] mountedPrefixParts int ) @@ -1140,7 +1160,7 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error { // serverErrorHandler is a wrapper around the application's error handler method // user for the fasthttp server configuration. It maps a set of fasthttp errors to fiber // errors before calling the application's error handler method. -func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { +func (app *App[TCtx]) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { // Acquire Ctx with fasthttp request from pool c := app.AcquireCtx(fctx) defer app.ReleaseCtx(c) @@ -1175,7 +1195,7 @@ func (app *App) serverErrorHandler(fctx *fasthttp.RequestCtx, err error) { } // startupProcess Is the method which executes all the necessary processes just before the start of the server. -func (app *App) startupProcess() *App { +func (app *App[TCtx]) startupProcess() *App[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -1188,7 +1208,7 @@ func (app *App) startupProcess() *App { } // Run onListen hooks. If they return an error, panic. -func (app *App) runOnListenHooks(listenData ListenData) { +func (app *App[TCtx]) runOnListenHooks(listenData ListenData) { if err := app.hooks.executeOnListenHooks(listenData); err != nil { panic(err) } diff --git a/ctx.go b/ctx.go index aecfacdcfd..bacaa495dd 100644 --- a/ctx.go +++ b/ctx.go @@ -47,10 +47,11 @@ const userContextKey contextKey = 0 // __local_user_context__ // generation tool `go install github.com/vburenin/ifacemaker@975a95966976eeb2d4365a7fb236e274c54da64c` // https://github.com/vburenin/ifacemaker/blob/975a95966976eeb2d4365a7fb236e274c54da64c/ifacemaker.go#L14-L30 // -//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface Ctx --pkg fiber --output ctx_interface_gen.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." +//go:generate ifacemaker --file ctx.go --struct DefaultCtx --iface CtxGeneric --pkg fiber --output ctx_interface.go --not-exported true --iface-comment "Ctx represents the Context which hold the HTTP request and response.\nIt has methods for the request query string, parameters, body, HTTP headers and so on." +//go:generate go run ctx_interface_gen.go type DefaultCtx struct { - app *App // Reference to *App - route *Route // Reference to *Route + app *App[*DefaultCtx] // Reference to *App + route *Route[*DefaultCtx] // Reference to *Route fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx bind *Bind // Default bind reference redirect *Redirect // Default redirect reference @@ -71,6 +72,8 @@ type DefaultCtx struct { matched bool // Non use route matched } +type Ctx = CtxGeneric[*DefaultCtx] + // SendFile defines configuration options when to transfer file with SendFile. type SendFile struct { // FS is the file system to serve the static files from. @@ -222,7 +225,7 @@ func (c *DefaultCtx) AcceptsLanguages(offers ...string) string { } // App returns the *App reference to the instance of the Fiber application -func (c *DefaultCtx) App() *App { +func (c *DefaultCtx) App() *App[*DefaultCtx] { return c.app } @@ -1045,6 +1048,7 @@ func (c *DefaultCtx) Next() error { } // Continue handler stack + // TODO: reduce this with generics if c.app.newCtxFunc != nil { _, err := c.app.nextCustom(c) return err @@ -1060,6 +1064,7 @@ func (c *DefaultCtx) RestartRouting() error { var err error c.indexRoute = -1 + // TODO: reduce this with generics if c.app.newCtxFunc != nil { _, err = c.app.nextCustom(c) } else { @@ -1731,7 +1736,7 @@ func (c *DefaultCtx) Stale() bool { // Status sets the HTTP status for the response. // This method is chainable. -func (c *DefaultCtx) Status(status int) Ctx { +func (c *DefaultCtx) Status(status int) *DefaultCtx { c.fasthttp.Response.SetStatusCode(status) return c } @@ -1776,7 +1781,7 @@ func (c *DefaultCtx) String() string { } // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. -func (c *DefaultCtx) Type(extension string, charset ...string) Ctx { +func (c *DefaultCtx) Type(extension string, charset ...string) *DefaultCtx { if len(charset) > 0 { c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0]) } else { @@ -1976,7 +1981,7 @@ func (c *DefaultCtx) setMatched(matched bool) { c.matched = matched } -func (c *DefaultCtx) setRoute(route *Route) { +func (c *DefaultCtx) setRoute(route *Route[*DefaultCtx]) { c.route = route } diff --git a/ctx_custom_interface.go b/ctx_custom_interface.go new file mode 100644 index 0000000000..01d86c0b2f --- /dev/null +++ b/ctx_custom_interface.go @@ -0,0 +1,70 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "errors" + + "github.com/valyala/fasthttp" +) + +type CustomCtx[T any] interface { + CtxGeneric[T] + + // Reset is a method to reset context fields by given request when to use server handlers. + Reset(fctx *fasthttp.RequestCtx) + + // Methods to use with next stack. + getMethodINT() int + getIndexRoute() int + getTreePath() string + getDetectionPath() string + getPathOriginal() string + getValues() *[maxParams]string + getMatched() bool + setIndexHandler(handler int) + setIndexRoute(route int) + setMatched(matched bool) + setRoute(route *Route[T]) +} + +func NewDefaultCtx[TCtx *DefaultCtx](app *App[*DefaultCtx]) TCtx { + // return ctx + return &DefaultCtx{ + // Set app reference + app: app, + } +} + +func (app *App[TCtx]) newCtx() CtxGeneric[TCtx] { + var c CtxGeneric[TCtx] + + // TODO: fix this with generics ? + if app.newCtxFunc != nil { + c = app.newCtxFunc(app) + } else { + c = NewDefaultCtx(app) + } + + return c +} + +// AcquireCtx retrieves a new Ctx from the pool. +func (app *App[TCtx]) AcquireCtx(fctx *fasthttp.RequestCtx) TCtx { + ctx, ok := app.pool.Get().(TCtx) + + if !ok { + panic(errors.New("failed to type-assert to Ctx")) + } + ctx.Reset(fctx) + + return ctx +} + +// ReleaseCtx releases the ctx back into the pool. +func (app *App[TCtx]) ReleaseCtx(c TCtx) { + c.release() + app.pool.Put(c) +} diff --git a/ctx_interface.go b/ctx_interface.go index ca438d82c5..20295c2df0 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -1,21 +1,343 @@ -// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ -// 🤖 Github Repository: https://github.com/gofiber/fiber -// 📌 API Documentation: https://docs.gofiber.io +// Code generated by ifacemaker; DO NOT EDIT. package fiber import ( - "errors" + "bufio" + "context" + "crypto/tls" + "io" + "mime/multipart" "github.com/valyala/fasthttp" ) -type CustomCtx interface { - Ctx - +// Ctx represents the Context which hold the HTTP request and response. +// It has methods for the request query string, parameters, body, HTTP headers and so on. +type CtxGeneric[T any] interface { + // Accepts checks if the specified extensions or content types are acceptable. + Accepts(offers ...string) string + // AcceptsCharsets checks if the specified charset is acceptable. + AcceptsCharsets(offers ...string) string + // AcceptsEncodings checks if the specified encoding is acceptable. + AcceptsEncodings(offers ...string) string + // AcceptsLanguages checks if the specified language is acceptable. + AcceptsLanguages(offers ...string) string + // App returns the *App[T] reference to the instance of the Fiber application + App() *App[T] + // Append the specified value to the HTTP response header field. + // If the header is not already set, it creates the header with the specified value. + Append(field string, values ...string) + // Attachment sets the HTTP response Content-Disposition header field to attachment. + Attachment(filename ...string) + // BaseURL returns (protocol + host + base path). + BaseURL() string + // BodyRaw contains the raw body submitted in a POST request. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + BodyRaw() []byte + tryDecodeBodyInOrder(originalBody *[]byte, encodings []string) ([]byte, uint8, error) + // Body contains the raw body submitted in a POST request. + // This method will decompress the body if the 'Content-Encoding' header is provided. + // It returns the original (or decompressed) body data which is valid only within the handler. + // Don't store direct references to the returned data. + // If you need to keep the body's data later, make a copy or use the Immutable option. + Body() []byte + // ClearCookie expires a specific cookie by key on the client side. + // If no key is provided it expires all cookies that came with the request. + ClearCookie(key ...string) + // RequestCtx returns *fasthttp.RequestCtx that carries a deadline + // a cancellation signal, and other values across API boundaries. + RequestCtx() *fasthttp.RequestCtx + // Context returns a context implementation that was set by + // user earlier or returns a non-nil, empty context,if it was not set earlier. + Context() context.Context + // SetContext sets a context implementation by user. + SetContext(ctx context.Context) + // Cookie sets a cookie by passing a cookie struct. + Cookie(cookie *Cookie) + // Cookies are used for getting a cookie value by key. + // Defaults to the empty string "" if the cookie doesn't exist. + // If a default value is given, it will return that value if the cookie doesn't exist. + // The returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Cookies(key string, defaultValue ...string) string + // Download transfers the file from path as an attachment. + // Typically, browsers will prompt the user for download. + // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog). + // Override this default with the filename parameter. + Download(file string, filename ...string) error + // Request return the *fasthttp.Request object + // This allows you to use all fasthttp request methods + // https://godoc.org/github.com/valyala/fasthttp#Request + Request() *fasthttp.Request + // Response return the *fasthttp.Response object + // This allows you to use all fasthttp response methods + // https://godoc.org/github.com/valyala/fasthttp#Response + Response() *fasthttp.Response + // Format performs content-negotiation on the Accept HTTP header. + // It uses Accepts to select a proper format and calls the matching + // user-provided handler function. + // If no accepted format is found, and a format with MediaType "default" is given, + // that default handler is called. If no format is found and no default is given, + // StatusNotAcceptable is sent. + Format(handlers ...ResFmt) error + // AutoFormat performs content-negotiation on the Accept HTTP header. + // It uses Accepts to select a proper format. + // The supported content types are text/html, text/plain, application/json, and application/xml. + // For more flexible content negotiation, use Format. + // If the header is not specified or there is no proper format, text/plain is used. + AutoFormat(body any) error + // FormFile returns the first file by key from a MultipartForm. + FormFile(key string) (*multipart.FileHeader, error) + // FormValue returns the first value by key from a MultipartForm. + // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. + // Defaults to the empty string "" if the form value doesn't exist. + // If a default value is given, it will return that value if the form value does not exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + FormValue(key string, defaultValue ...string) string + // Fresh returns true when the response is still “fresh” in the client's cache, + // otherwise false is returned to indicate that the client cache is now stale + // and the full response should be sent. + // When a client sends the Cache-Control: no-cache request header to indicate an end-to-end + // reload request, this module will return false to make handling these requests transparent. + // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33 + Fresh() bool + // Get returns the HTTP request header specified by field. + // Field names are case-insensitive + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + Get(key string, defaultValue ...string) string + // GetRespHeader returns the HTTP response header specified by field. + // Field names are case-insensitive + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetRespHeader(key string, defaultValue ...string) string + // GetRespHeaders returns the HTTP response headers. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetRespHeaders() map[string][]string + // GetReqHeaders returns the HTTP request headers. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting instead. + GetReqHeaders() map[string][]string + // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. + // Returned value is only valid within the handler. Do not store any references. + // In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, + // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. + // Example: URL: https://example.com:8080 -> Host: example.com:8080 + // Make copies or use the Immutable setting instead. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + Host() string + // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. + // Returned value is only valid within the handler. Do not store any references. + // Example: URL: https://example.com:8080 -> Hostname: example.com + // Make copies or use the Immutable setting instead. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + Hostname() string + // Port returns the remote port of the request. + Port() string + // IP returns the remote IP address of the request. + // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + IP() string + // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. + // When IP validation is enabled, any invalid IPs will be omitted. + extractIPsFromHeader(header string) []string + // extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. + // currently, it will return the first valid IP address in header. + // when IP validation is disabled, it will simply return the value of the header without any inspection. + // Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. + extractIPFromHeader(header string) string + // IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. + // When IP validation is enabled, only valid IPs are returned. + IPs() []string + // Is returns the matching content type, + // if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter + Is(extension string) bool + // JSON converts any interface or string to JSON. + // Array and slice values encode as JSON arrays, + // except that []byte encodes as a base64-encoded string, + // and a nil slice encodes as the null JSON value. + // If the ctype parameter is given, this method will set the + // Content-Type header equal to ctype. If ctype is not given, + // The Content-Type header will be set to application/json. + JSON(data any, ctype ...string) error + // CBOR converts any interface or string to CBOR encoded bytes. + // If the ctype parameter is given, this method will set the + // Content-Type header equal to ctype. If ctype is not given, + // The Content-Type header will be set to application/cbor. + CBOR(data any, ctype ...string) error + // JSONP sends a JSON response with JSONP support. + // This method is identical to JSON, except that it opts-in to JSONP callback support. + // By default, the callback name is simply callback. + JSONP(data any, callback ...string) error + // XML converts any interface or string to XML. + // This method also sets the content header to application/xml. + XML(data any) error + // Links joins the links followed by the property to populate the response's Link HTTP header field. + Links(link ...string) + // Locals makes it possible to pass any values under keys scoped to the request + // and therefore available to all following routes that match the request. + // + // All the values are removed from ctx after returning from the top + // RequestHandler. Additionally, Close method is called on each value + // implementing io.Closer before removing the value from ctx. + Locals(key any, value ...any) any + // Location sets the response Location HTTP header to the specified path parameter. + Location(path string) + // Method returns the HTTP request method for the context, optionally overridden by the provided argument. + // If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. + // Otherwise, it updates the context's method and returns the overridden method as a string. + Method(override ...string) string + // MultipartForm parse form entries from binary. + // This returns a map[string][]string, so given a key the value will be a string slice. + MultipartForm() (*multipart.Form, error) + // ClientHelloInfo return CHI from context + ClientHelloInfo() *tls.ClientHelloInfo + // Next executes the next method in the stack that matches the current route. + Next() error + // RestartRouting instead of going to the next handler. This may be useful after + // changing the request path. Note that handlers might be executed again. + RestartRouting() error + // OriginalURL contains the original request URL. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + OriginalURL() string + // Params is used to get the route parameters. + // Defaults to empty string "" if the param doesn't exist. + // If a default value is given, it will return that value if the param doesn't exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Params(key string, defaultValue ...string) string + // Path returns the path part of the request URL. + // Optionally, you could override the path. + Path(override ...string) string + // Scheme contains the request protocol string: http or https for TLS requests. + // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. + Scheme() string + // Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. + Protocol() string + // Query returns the query string parameter in the url. + // Defaults to empty string "" if the query doesn't exist. + // If a default value is given, it will return that value if the query doesn't exist. + // Returned value is only valid within the handler. Do not store any references. + // Make copies or use the Immutable setting to use the value outside the Handler. + Query(key string, defaultValue ...string) string + // Queries returns a map of query parameters and their values. + // + // GET /?name=alex&wanna_cake=2&id= + // Queries()["name"] == "alex" + // Queries()["wanna_cake"] == "2" + // Queries()["id"] == "" + // + // GET /?field1=value1&field1=value2&field2=value3 + // Queries()["field1"] == "value2" + // Queries()["field2"] == "value3" + // + // GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 + // Queries()["list_a"] == "3" + // Queries()["list_b[]"] == "3" + // Queries()["list_c"] == "1,2,3" + // + // GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending + // Queries()["filters.author.name"] == "John" + // Queries()["filters.category.name"] == "Technology" + // Queries()["filters[customer][name]"] == "Alice" + // Queries()["filters[status]"] == "pending" + Queries() map[string]string + // Range returns a struct containing the type and a slice of ranges. + Range(size int) (Range, error) + // Redirect returns the Redirect reference. + // Use Redirect().Status() to set custom redirection status code. + // If status is not specified, status defaults to 302 Found. + // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. + Redirect() *Redirect + // ViewBind Add vars to default view var map binding to template engine. + // Variables are read by the Render method and may be overwritten. + ViewBind(vars Map) error + // getLocationFromRoute get URL location from route using parameters + getLocationFromRoute(route Route[T], params Map) (string, error) + // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" + GetRouteURL(routeName string, params Map) (string, error) + // Render a template with data and sends a text/html response. + // We support the following engines: https://github.com/gofiber/template + Render(name string, bind any, layouts ...string) error + renderExtensions(bind any) + // Route returns the matched Route struct. + Route() *Route[T] + // SaveFile saves any multipart file to disk. + SaveFile(fileheader *multipart.FileHeader, path string) error + // SaveFileToStorage saves any multipart file to an external storage system. + SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error + // Secure returns whether a secure connection was established. + Secure() bool + // Send sets the HTTP response body without copying it. + // From this point onward the body argument must not be changed. + Send(body []byte) error + // SendFile transfers the file from the specified path. + // By default, the file is not compressed. To enable compression, set SendFile.Compress to true. + // The Content-Type response HTTP header field is set based on the file's extension. + // If the file extension is missing or invalid, the Content-Type is detected from the file's format. + SendFile(file string, config ...SendFile) error + // SendStatus sets the HTTP status code and if the response body is empty, + // it sets the correct status message in the body. + SendStatus(status int) error + // SendString sets the HTTP response body for string types. + // This means no type assertion, recommended for faster performance + SendString(body string) error + // SendStream sets response body stream and optional body size. + SendStream(stream io.Reader, size ...int) error + // SendStreamWriter sets response body stream writer + SendStreamWriter(streamWriter func(*bufio.Writer)) error + // Set sets the response's HTTP header field to the specified key, value. + Set(key, val string) + setCanonical(key, val string) + // Subdomains returns a string slice of subdomains in the domain name of the request. + // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. + Subdomains(offset ...int) []string + // Stale is not implemented yet, pull requests are welcome! + Stale() bool + // Status sets the HTTP status for the response. + // This method is chainable. + Status(status int) T + // String returns unique string representation of the ctx. + // + // The returned value may be useful for logging. + String() string + // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. + Type(extension string, charset ...string) T + // Vary adds the given header field to the Vary response header. + // This will append the header, if not already listed, otherwise leaves it listed in the current location. + Vary(fields ...string) + // Write appends p into response body. + Write(p []byte) (int, error) + // Writef appends f & a into response body writer. + Writef(f string, a ...any) (int, error) + // WriteString appends s to response body. + WriteString(s string) (int, error) + // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, + // indicating that the request was issued by a client library (such as jQuery). + XHR() bool + // configDependentPaths set paths for route recognition and prepared paths for the user, + // here the features for caseSensitive, decoded paths, strict paths are evaluated + configDependentPaths() + // IsProxyTrusted checks trustworthiness of remote ip. + // If Config.TrustProxy false, it returns true + // IsProxyTrusted can check remote ip by proxy ranges and ip map. + IsProxyTrusted() bool + // IsFromLocal will return true if request came from local. + IsFromLocal() bool + // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. + // It gives custom binding support, detailed binding options and more. + // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser + Bind() *Bind // Reset is a method to reset context fields by given request when to use server handlers. Reset(fctx *fasthttp.RequestCtx) - + // Release is a method to reset context fields when to use ReleaseCtx() + release() + getBody() []byte // Methods to use with next stack. getMethodINT() int getIndexRoute() int @@ -27,43 +349,11 @@ type CustomCtx interface { setIndexHandler(handler int) setIndexRoute(route int) setMatched(matched bool) - setRoute(route *Route) -} - -func NewDefaultCtx(app *App) *DefaultCtx { - // return ctx - return &DefaultCtx{ - // Set app reference - app: app, - } -} - -func (app *App) newCtx() Ctx { - var c Ctx - - if app.newCtxFunc != nil { - c = app.newCtxFunc(app) - } else { - c = NewDefaultCtx(app) - } - - return c -} - -// AcquireCtx retrieves a new Ctx from the pool. -func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx { - ctx, ok := app.pool.Get().(Ctx) - - if !ok { - panic(errors.New("failed to type-assert to Ctx")) - } - ctx.Reset(fctx) - - return ctx -} - -// ReleaseCtx releases the ctx back into the pool. -func (app *App) ReleaseCtx(c Ctx) { - c.release() - app.pool.Put(c) + setRoute(route *Route[T]) + // 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 + // End immediately flushes the current response and closes the underlying connection. + End() error } diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index 101068a269..986f23cc4d 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -1,359 +1,78 @@ -// Code generated by ifacemaker; DO NOT EDIT. +//go:build ignore -package fiber +package main import ( "bufio" - "context" - "crypto/tls" + "bytes" + "fmt" "io" - "mime/multipart" - - "github.com/valyala/fasthttp" + "os" + "regexp" + "strings" ) -// Ctx represents the Context which hold the HTTP request and response. -// It has methods for the request query string, parameters, body, HTTP headers and so on. -type Ctx interface { - // Accepts checks if the specified extensions or content types are acceptable. - Accepts(offers ...string) string - // AcceptsCharsets checks if the specified charset is acceptable. - AcceptsCharsets(offers ...string) string - // AcceptsEncodings checks if the specified encoding is acceptable. - AcceptsEncodings(offers ...string) string - // AcceptsLanguages checks if the specified language is acceptable. - AcceptsLanguages(offers ...string) string - // App returns the *App reference to the instance of the Fiber application - App() *App - // Append the specified value to the HTTP response header field. - // If the header is not already set, it creates the header with the specified value. - Append(field string, values ...string) - // Attachment sets the HTTP response Content-Disposition header field to attachment. - Attachment(filename ...string) - // BaseURL returns (protocol + host + base path). - BaseURL() string - // BodyRaw contains the raw body submitted in a POST request. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - BodyRaw() []byte - tryDecodeBodyInOrder(originalBody *[]byte, encodings []string) ([]byte, uint8, error) - // Body contains the raw body submitted in a POST request. - // This method will decompress the body if the 'Content-Encoding' header is provided. - // It returns the original (or decompressed) body data which is valid only within the handler. - // Don't store direct references to the returned data. - // If you need to keep the body's data later, make a copy or use the Immutable option. - Body() []byte - // ClearCookie expires a specific cookie by key on the client side. - // If no key is provided it expires all cookies that came with the request. - ClearCookie(key ...string) - // RequestCtx returns *fasthttp.RequestCtx that carries a deadline - // a cancellation signal, and other values across API boundaries. - RequestCtx() *fasthttp.RequestCtx - // Context returns a context implementation that was set by - // user earlier or returns a non-nil, empty context,if it was not set earlier. - Context() context.Context - // SetContext sets a context implementation by user. - SetContext(ctx context.Context) - // Cookie sets a cookie by passing a cookie struct. - Cookie(cookie *Cookie) - // Cookies are used for getting a cookie value by key. - // Defaults to the empty string "" if the cookie doesn't exist. - // If a default value is given, it will return that value if the cookie doesn't exist. - // The returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Cookies(key string, defaultValue ...string) string - // Download transfers the file from path as an attachment. - // Typically, browsers will prompt the user for download. - // By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog). - // Override this default with the filename parameter. - Download(file string, filename ...string) error - // Request return the *fasthttp.Request object - // This allows you to use all fasthttp request methods - // https://godoc.org/github.com/valyala/fasthttp#Request - Request() *fasthttp.Request - // Response return the *fasthttp.Response object - // This allows you to use all fasthttp response methods - // https://godoc.org/github.com/valyala/fasthttp#Response - Response() *fasthttp.Response - // Format performs content-negotiation on the Accept HTTP header. - // It uses Accepts to select a proper format and calls the matching - // user-provided handler function. - // If no accepted format is found, and a format with MediaType "default" is given, - // that default handler is called. If no format is found and no default is given, - // StatusNotAcceptable is sent. - Format(handlers ...ResFmt) error - // AutoFormat performs content-negotiation on the Accept HTTP header. - // It uses Accepts to select a proper format. - // The supported content types are text/html, text/plain, application/json, and application/xml. - // For more flexible content negotiation, use Format. - // If the header is not specified or there is no proper format, text/plain is used. - AutoFormat(body any) error - // FormFile returns the first file by key from a MultipartForm. - FormFile(key string) (*multipart.FileHeader, error) - // FormValue returns the first value by key from a MultipartForm. - // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. - // Defaults to the empty string "" if the form value doesn't exist. - // If a default value is given, it will return that value if the form value does not exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - FormValue(key string, defaultValue ...string) string - // Fresh returns true when the response is still “fresh” in the client's cache, - // otherwise false is returned to indicate that the client cache is now stale - // and the full response should be sent. - // When a client sends the Cache-Control: no-cache request header to indicate an end-to-end - // reload request, this module will return false to make handling these requests transparent. - // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33 - Fresh() bool - // Get returns the HTTP request header specified by field. - // Field names are case-insensitive - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - Get(key string, defaultValue ...string) string - // GetRespHeader returns the HTTP response header specified by field. - // Field names are case-insensitive - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetRespHeader(key string, defaultValue ...string) string - // GetRespHeaders returns the HTTP response headers. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetRespHeaders() map[string][]string - // GetReqHeaders returns the HTTP request headers. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting instead. - GetReqHeaders() map[string][]string - // Host contains the host derived from the X-Forwarded-Host or Host HTTP header. - // Returned value is only valid within the handler. Do not store any references. - // In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, - // while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. - // Example: URL: https://example.com:8080 -> Host: example.com:8080 - // Make copies or use the Immutable setting instead. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - Host() string - // Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. - // Returned value is only valid within the handler. Do not store any references. - // Example: URL: https://example.com:8080 -> Hostname: example.com - // Make copies or use the Immutable setting instead. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - Hostname() string - // Port returns the remote port of the request. - Port() string - // IP returns the remote IP address of the request. - // If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - IP() string - // extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. - // When IP validation is enabled, any invalid IPs will be omitted. - extractIPsFromHeader(header string) []string - // extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. - // currently, it will return the first valid IP address in header. - // when IP validation is disabled, it will simply return the value of the header without any inspection. - // Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. - extractIPFromHeader(header string) string - // IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. - // When IP validation is enabled, only valid IPs are returned. - IPs() []string - // Is returns the matching content type, - // if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter - Is(extension string) bool - // JSON converts any interface or string to JSON. - // Array and slice values encode as JSON arrays, - // except that []byte encodes as a base64-encoded string, - // and a nil slice encodes as the null JSON value. - // If the ctype parameter is given, this method will set the - // Content-Type header equal to ctype. If ctype is not given, - // The Content-Type header will be set to application/json. - JSON(data any, ctype ...string) error - // CBOR converts any interface or string to CBOR encoded bytes. - // If the ctype parameter is given, this method will set the - // Content-Type header equal to ctype. If ctype is not given, - // The Content-Type header will be set to application/cbor. - CBOR(data any, ctype ...string) error - // JSONP sends a JSON response with JSONP support. - // This method is identical to JSON, except that it opts-in to JSONP callback support. - // By default, the callback name is simply callback. - JSONP(data any, callback ...string) error - // XML converts any interface or string to XML. - // This method also sets the content header to application/xml. - XML(data any) error - // Links joins the links followed by the property to populate the response's Link HTTP header field. - Links(link ...string) - // Locals makes it possible to pass any values under keys scoped to the request - // and therefore available to all following routes that match the request. - // - // All the values are removed from ctx after returning from the top - // RequestHandler. Additionally, Close method is called on each value - // implementing io.Closer before removing the value from ctx. - Locals(key any, value ...any) any - // Location sets the response Location HTTP header to the specified path parameter. - Location(path string) - // Method returns the HTTP request method for the context, optionally overridden by the provided argument. - // If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. - // Otherwise, it updates the context's method and returns the overridden method as a string. - Method(override ...string) string - // MultipartForm parse form entries from binary. - // This returns a map[string][]string, so given a key the value will be a string slice. - MultipartForm() (*multipart.Form, error) - // ClientHelloInfo return CHI from context - ClientHelloInfo() *tls.ClientHelloInfo - // Next executes the next method in the stack that matches the current route. - Next() error - // RestartRouting instead of going to the next handler. This may be useful after - // changing the request path. Note that handlers might be executed again. - RestartRouting() error - // OriginalURL contains the original request URL. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - OriginalURL() string - // Params is used to get the route parameters. - // Defaults to empty string "" if the param doesn't exist. - // If a default value is given, it will return that value if the param doesn't exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Params(key string, defaultValue ...string) string - // Path returns the path part of the request URL. - // Optionally, you could override the path. - Path(override ...string) string - // Scheme contains the request protocol string: http or https for TLS requests. - // Please use Config.TrustProxy to prevent header spoofing, in case when your app is behind the proxy. - Scheme() string - // Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. - Protocol() string - // Query returns the query string parameter in the url. - // Defaults to empty string "" if the query doesn't exist. - // If a default value is given, it will return that value if the query doesn't exist. - // Returned value is only valid within the handler. Do not store any references. - // Make copies or use the Immutable setting to use the value outside the Handler. - Query(key string, defaultValue ...string) string - // Queries returns a map of query parameters and their values. - // - // GET /?name=alex&wanna_cake=2&id= - // Queries()["name"] == "alex" - // Queries()["wanna_cake"] == "2" - // Queries()["id"] == "" - // - // GET /?field1=value1&field1=value2&field2=value3 - // Queries()["field1"] == "value2" - // Queries()["field2"] == "value3" - // - // GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 - // Queries()["list_a"] == "3" - // Queries()["list_b[]"] == "3" - // Queries()["list_c"] == "1,2,3" - // - // GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending - // Queries()["filters.author.name"] == "John" - // Queries()["filters.category.name"] == "Technology" - // Queries()["filters[customer][name]"] == "Alice" - // Queries()["filters[status]"] == "pending" - Queries() map[string]string - // Range returns a struct containing the type and a slice of ranges. - Range(size int) (Range, error) - // Redirect returns the Redirect reference. - // Use Redirect().Status() to set custom redirection status code. - // If status is not specified, status defaults to 302 Found. - // You can use Redirect().To(), Redirect().Route() and Redirect().Back() for redirection. - Redirect() *Redirect - // ViewBind Add vars to default view var map binding to template engine. - // Variables are read by the Render method and may be overwritten. - ViewBind(vars Map) error - // getLocationFromRoute get URL location from route using parameters - getLocationFromRoute(route Route, params Map) (string, error) - // GetRouteURL generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" - GetRouteURL(routeName string, params Map) (string, error) - // Render a template with data and sends a text/html response. - // We support the following engines: https://github.com/gofiber/template - Render(name string, bind any, layouts ...string) error - renderExtensions(bind any) - // Route returns the matched Route struct. - Route() *Route - // SaveFile saves any multipart file to disk. - SaveFile(fileheader *multipart.FileHeader, path string) error - // SaveFileToStorage saves any multipart file to an external storage system. - SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error - // Secure returns whether a secure connection was established. - Secure() bool - // Send sets the HTTP response body without copying it. - // From this point onward the body argument must not be changed. - Send(body []byte) error - // SendFile transfers the file from the specified path. - // By default, the file is not compressed. To enable compression, set SendFile.Compress to true. - // The Content-Type response HTTP header field is set based on the file's extension. - // If the file extension is missing or invalid, the Content-Type is detected from the file's format. - SendFile(file string, config ...SendFile) error - // SendStatus sets the HTTP status code and if the response body is empty, - // it sets the correct status message in the body. - SendStatus(status int) error - // SendString sets the HTTP response body for string types. - // This means no type assertion, recommended for faster performance - SendString(body string) error - // SendStream sets response body stream and optional body size. - SendStream(stream io.Reader, size ...int) error - // SendStreamWriter sets response body stream writer - SendStreamWriter(streamWriter func(*bufio.Writer)) error - // Set sets the response's HTTP header field to the specified key, value. - Set(key, val string) - setCanonical(key, val string) - // Subdomains returns a string slice of subdomains in the domain name of the request. - // The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. - Subdomains(offset ...int) []string - // Stale is not implemented yet, pull requests are welcome! - Stale() bool - // Status sets the HTTP status for the response. - // This method is chainable. - Status(status int) Ctx - // String returns unique string representation of the ctx. - // - // The returned value may be useful for logging. - String() string - // Type sets the Content-Type HTTP header to the MIME type specified by the file extension. - Type(extension string, charset ...string) Ctx - // Vary adds the given header field to the Vary response header. - // This will append the header, if not already listed, otherwise leaves it listed in the current location. - Vary(fields ...string) - // Write appends p into response body. - Write(p []byte) (int, error) - // Writef appends f & a into response body writer. - Writef(f string, a ...any) (int, error) - // WriteString appends s to response body. - WriteString(s string) (int, error) - // XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, - // indicating that the request was issued by a client library (such as jQuery). - XHR() bool - // configDependentPaths set paths for route recognition and prepared paths for the user, - // here the features for caseSensitive, decoded paths, strict paths are evaluated - configDependentPaths() - // IsProxyTrusted checks trustworthiness of remote ip. - // If Config.TrustProxy false, it returns true - // IsProxyTrusted can check remote ip by proxy ranges and ip map. - IsProxyTrusted() bool - // IsFromLocal will return true if request came from local. - IsFromLocal() bool - // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. - // It gives custom binding support, detailed binding options and more. - // Replacement of: BodyParser, ParamsParser, GetReqHeaders, GetRespHeaders, AllParams, QueryParser, ReqHeaderParser - Bind() *Bind - // Reset is a method to reset context fields by given request when to use server handlers. - Reset(fctx *fasthttp.RequestCtx) - // Release is a method to reset context fields when to use ReleaseCtx() - release() - getBody() []byte - // Methods to use with next stack. - getMethodINT() int - getIndexRoute() int - getTreePath() string - getDetectionPath() string - getPathOriginal() string - getValues() *[maxParams]string - getMatched() bool - setIndexHandler(handler int) - setIndexRoute(route int) - setMatched(matched bool) - 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 - // End immediately flushes the current response and closes the underlying connection. - End() error +func main() { + const filename = "ctx_interface.go" + + // 1) read file + data, err := os.ReadFile(filename) + if err != nil { + panic(fmt.Errorf("failed to read file: %w", err)) + } + + // 2) patch interface + patched, err := patchCtxFile(data) + if err != nil { + panic(err) + } + + // 3) write patched file + if err := os.WriteFile(filename, patched, 0o644); err != nil { + panic(fmt.Errorf("failed to write patched file: %w", err)) + } +} + +// patchCtxFile adjust the Ctx interface in the given file +func patchCtxFile(input []byte) ([]byte, error) { + // process file line by line + in := bytes.NewReader(input) + scanner := bufio.NewScanner(in) + var outBuf bytes.Buffer + + regexCtx := regexp.MustCompile(`(\*Default)Ctx`) + regexApp := regexp.MustCompile(`\*App(\[\w+])?`) + + for scanner.Scan() { + line := scanner.Text() + + // A) change interface head definition + // => "type Ctx interface {" -> "type Ctx[T any] interface {" + if strings.HasPrefix(line, "type") { + line = strings.Replace(line, + "type CtxGeneric interface {", + "type CtxGeneric[T any] interface {", + 1, + ) + } else { + // B) replace every use of Ctx with T but only in the function definitions + // via regex and boundary word matching + // => "func (app *App[TCtx]) newCtx() Ctx {" -> "func (app *App[TCtx]) newCtx() T {" + if strings.Contains(line, "Ctx") { + line = regexCtx.ReplaceAllString(line, "T") + } + + // C) App with generic type + if strings.Contains(line, "App") { + line = regexApp.ReplaceAllString(line, "*App[T]") + } + } + + outBuf.WriteString(line + "\n") + } + if err := scanner.Err(); err != nil && err != io.EOF { + return nil, fmt.Errorf("scanner error: %w", err) + } + + return outBuf.Bytes(), nil } diff --git a/ctx_test.go b/ctx_test.go index 082b0d442c..281d31ab70 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -111,7 +111,7 @@ func Test_Ctx_CustomCtx(t *testing.T) { app := New() - app.NewCtxFunc(func(app *App) CustomCtx { + app.NewCtxFunc(func(app *App[TCtx]) CustomCtx { return &customCtx{ DefaultCtx: *NewDefaultCtx(app), } @@ -138,7 +138,7 @@ func Test_Ctx_CustomCtx_and_Method(t *testing.T) { }) // Create custom context - app.NewCtxFunc(func(app *App) CustomCtx { + app.NewCtxFunc(func(app *App[TCtx]) CustomCtx { return &customCtx{ DefaultCtx: *NewDefaultCtx(app), } @@ -262,7 +262,7 @@ func Test_Ctx_App(t *testing.T) { app.config.BodyLimit = 1000 c := app.AcquireCtx(&fasthttp.RequestCtx{}) - require.Equal(t, 1000, c.App().config.BodyLimit) + require.Equal(t, 1000, app.config.BodyLimit) } // go test -run Test_Ctx_Append diff --git a/docs/api/app.md b/docs/api/app.md index 23171a24a3..94764dacfc 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -48,7 +48,7 @@ func main() { The `MountPath` property contains one or more path patterns on which a sub-app was mounted. ```go title="Signature" -func (app *App) MountPath() string +func (app *App[TCtx]) MountPath() string ``` ```go title="Example" @@ -87,7 +87,7 @@ Mounting order is important for `MountPath`. To get mount paths properly, you sh You can group routes by creating a `*Group` struct. ```go title="Signature" -func (app *App) Group(prefix string, handlers ...Handler) Router +func (app *App[TCtx]) Group(prefix string, handlers ...Handler) Router ``` ```go title="Example" @@ -127,7 +127,7 @@ Returns an instance of a single route, which you can then use to handle HTTP ver Similar to [`Express`](https://expressjs.com/de/api.html#app.route). ```go title="Signature" -func (app *App) Route(path string) Register +func (app *App[TCtx]) Route(path string) Register ```
@@ -204,7 +204,7 @@ func main() { This method returns the number of registered handlers. ```go title="Signature" -func (app *App) HandlersCount() uint32 +func (app *App[TCtx]) HandlersCount() uint32 ``` ### Stack @@ -212,7 +212,7 @@ func (app *App) HandlersCount() uint32 This method returns the original router stack. ```go title="Signature" -func (app *App) Stack() [][]*Route +func (app *App[TCtx]) Stack() [][]*Route ``` ```go title="Example" @@ -280,7 +280,7 @@ func main() { This method assigns the name to the latest created route. ```go title="Signature" -func (app *App) Name(name string) Router +func (app *App[TCtx]) Name(name string) Router ``` ```go title="Example" @@ -514,6 +514,8 @@ func (app *App) ErrorHandler(ctx Ctx, err error) error ## NewCtxFunc +TODO: remove this section and replace with the new fiber.NewWithCustomCtx ... + `NewCtxFunc` allows you to customize the `ctx` struct as needed. ```go title="Signature" diff --git a/docs/api/fiber.md b/docs/api/fiber.md index a79b2ba0c3..e8ac7e4458 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -103,7 +103,7 @@ app.Listen(":8080", fiber.ListenConfig{ | Property | Type | Description | Default | |-------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|---------| -| BeforeServeFunc | `func(app *App) error` | Allows customizing and accessing fiber app before serving the app. | `nil` | +| BeforeServeFunc | `func(app *App[TCtx]) error` | Allows customizing and accessing fiber app before serving the app. | `nil` | | CertClientFile | `string` | Path of the client certificate. If you want to use mTLS, you must enter this field. | `""` | | CertFile | `string` | Path of the certificate file. If you want to use TLS, you must enter this field. | `""` | | CertKeyFile | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field. | `""` | diff --git a/docs/partials/routing/handler.md b/docs/partials/routing/handler.md index 8a0a1e09cd..af0148274f 100644 --- a/docs/partials/routing/handler.md +++ b/docs/partials/routing/handler.md @@ -9,22 +9,22 @@ Registers a route bound to a specific [HTTP method](https://developer.mozilla.or ```go title="Signatures" // HTTP methods -func (app *App) Get(path string, handler Handler, handlers ...Handler) Router -func (app *App) Head(path string, handler Handler, handlers ...Handler) Router -func (app *App) Post(path string, handler Handler, handlers ...Handler) Router -func (app *App) Put(path string, handler Handler, handlers ...Handler) Router -func (app *App) Delete(path string, handler Handler, handlers ...Handler) Router -func (app *App) Connect(path string, handler Handler, handlers ...Handler) Router -func (app *App) Options(path string, handler Handler, handlers ...Handler) Router -func (app *App) Trace(path string, handler Handler, handlers ...Handler) Router -func (app *App) Patch(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Get(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Head(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Post(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Put(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Delete(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Connect(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Options(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Trace(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Patch(path string, handler Handler, handlers ...Handler) Router // Add allows you to specify a method as value -func (app *App) Add(method, path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Add(method, path string, handler Handler, handlers ...Handler) Router // All will register the route on all HTTP methods // Almost the same as app.Use but not bound to prefixes -func (app *App) All(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) All(path string, handler Handler, handlers ...Handler) Router ``` ```go title="Examples" @@ -44,13 +44,13 @@ app.Post("/api/register", func(c fiber.Ctx) error { Can be used for middleware packages and prefix catchers. These routes will only match the beginning of each path i.e. `/john` will match `/john/doe`, `/johnnnnn` etc ```go title="Signature" -func (app *App) Use(args ...any) Router +func (app *App[TCtx]) Use(args ...any) Router // Different usage variations -func (app *App) Use(handler Handler, handlers ...Handler) Router -func (app *App) Use(path string, handler Handler, handlers ...Handler) Router -func (app *App) Use(paths []string, handler Handler, handlers ...Handler) Router -func (app *App) Use(path string, app *App) Router +func (app *App[TCtx]) Use(handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Use(path string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Use(paths []string, handler Handler, handlers ...Handler) Router +func (app *App[TCtx]) Use(path string, app *App) Router ``` ```go title="Examples" diff --git a/docs/whats_new.md b/docs/whats_new.md index 4185a7e3ad..7d8a15ce5d 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -75,6 +75,8 @@ We have made several changes to the Fiber app, including: ### Custom Ctx Interface in Fiber v3 +TODO - Add more details + Fiber v3 introduces a customizable `Ctx` interface, allowing developers to extend and modify the context to fit their needs. This feature provides greater flexibility and control over request handling. #### Idea Behind Custom Ctx Classes @@ -83,10 +85,12 @@ The idea behind custom `Ctx` classes is to give developers the ability to extend #### NewCtxFunc +TODO change example + The `NewCtxFunc` method allows you to customize the `Ctx` struct as needed. ```go title="Signature" -func (app *App) NewCtxFunc(function func(app *App) CustomCtx) +func (app *App[TCtx]) NewCtxFunc(function func(app *App) CustomCtx) ```
diff --git a/group.go b/group.go index fe5cc8d014..ce27862bb3 100644 --- a/group.go +++ b/group.go @@ -10,9 +10,9 @@ import ( ) // Group struct -type Group struct { - app *App - parentGroup *Group +type Group[TCtx CtxGeneric[TCtx]] struct { + app *App[TCtx] + parentGroup *Group[TCtx] name string Prefix string @@ -23,7 +23,7 @@ type Group struct { // // If this method is used before any route added to group, it'll set group name and OnGroupNameHook will be used. // Otherwise, it'll set route name and OnName hook will be used. -func (grp *Group) Name(name string) Router { +func (grp *Group[TCtx]) Name(name string) Router[TCtx] { if grp.anyRouteDefined { grp.app.Name(name) @@ -66,21 +66,21 @@ func (grp *Group) Name(name string) Router { // app.Use("/mounted-path", subApp) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (grp *Group) Use(args ...any) Router { - var subApp *App +func (grp *Group[TCtx]) Use(args ...any) Router[TCtx] { + var subApp *App[TCtx] var prefix string var prefixes []string - var handlers []Handler + var handlers []Handler[TCtx] for i := 0; i < len(args); i++ { switch arg := args[i].(type) { case string: prefix = arg - case *App: + case *App[TCtx]: subApp = arg case []string: prefixes = arg - case Handler: + case Handler[TCtx]: handlers = append(handlers, arg) default: panic(fmt.Sprintf("use: invalid handler %v\n", reflect.TypeOf(arg))) @@ -109,59 +109,59 @@ func (grp *Group) Use(args ...any) Router { // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (grp *Group) Get(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Get(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodGet}, path, handler, handlers...) } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (grp *Group) Head(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Head(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodHead}, path, handler, handlers...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (grp *Group) Post(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Post(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPost}, path, handler, handlers...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (grp *Group) Put(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Put(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPut}, path, handler, handlers...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (grp *Group) Delete(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Delete(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodDelete}, path, handler, handlers...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (grp *Group) Connect(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Connect(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodConnect}, path, handler, handlers...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (grp *Group) Options(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Options(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodOptions}, path, handler, handlers...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the path to the target resource. -func (grp *Group) Trace(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Trace(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodTrace}, path, handler, handlers...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (grp *Group) Patch(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Patch(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { return grp.Add([]string{MethodPatch}, path, handler, handlers...) } // Add allows you to specify multiple HTTP methods to register a route. -func (grp *Group) Add(methods []string, path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) Add(methods []string, path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { grp.app.register(methods, getGroupPath(grp.Prefix, path), grp, append([]Handler{handler}, handlers...)...) if !grp.anyRouteDefined { grp.anyRouteDefined = true @@ -171,7 +171,7 @@ func (grp *Group) Add(methods []string, path string, handler Handler, handlers . } // All will register the handler on all HTTP methods -func (grp *Group) All(path string, handler Handler, handlers ...Handler) Router { +func (grp *Group[TCtx]) All(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] { _ = grp.Add(grp.app.config.RequestMethods, path, handler, handlers...) return grp } @@ -180,14 +180,14 @@ func (grp *Group) All(path string, handler Handler, handlers ...Handler) Router // // api := app.Group("/api") // api.Get("/users", handler) -func (grp *Group) Group(prefix string, handlers ...Handler) Router { +func (grp *Group[TCtx]) Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] { prefix = getGroupPath(grp.Prefix, prefix) if len(handlers) > 0 { grp.app.register([]string{methodUse}, prefix, grp, handlers...) } // Create new group - newGrp := &Group{Prefix: prefix, app: grp.app, parentGroup: grp} + newGrp := &Group[TCtx]{Prefix: prefix, app: grp.app, parentGroup: grp} if err := grp.app.hooks.executeOnGroupHooks(*newGrp); err != nil { panic(err) } @@ -197,9 +197,9 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router { // Route is used to define routes with a common prefix inside the common function. // Uses Group method to define new sub-router. -func (grp *Group) Route(path string) Register { +func (grp *Group[TCtx]) Route(path string) Register[TCtx] { // Create new group - register := &Registering{app: grp.app, path: getGroupPath(grp.Prefix, path)} + register := &Registering[TCtx]{app: grp.app, path: getGroupPath(grp.Prefix, path)} return register } diff --git a/helpers.go b/helpers.go index 18728e563a..07ad87ee3f 100644 --- a/helpers.go +++ b/helpers.go @@ -93,7 +93,7 @@ func readContent(rf io.ReaderFrom, name string) (int64, error) { } // quoteString escape special characters in a given string -func (app *App) quoteString(raw string) string { +func (app *App[TCtx]) quoteString(raw string) string { bb := bytebufferpool.Get() quoted := app.getString(fasthttp.AppendQuotedArg(bb.B, app.getBytes(raw))) bytebufferpool.Put(bb) @@ -101,7 +101,7 @@ func (app *App) quoteString(raw string) string { } // Scan stack if other methods match the request -func (app *App) methodExist(c *DefaultCtx) bool { +func (app *App[TCtx]) methodExist(c *DefaultCtx) bool { var exists bool methods := app.config.RequestMethods @@ -113,9 +113,9 @@ func (app *App) methodExist(c *DefaultCtx) bool { // Reset stack index c.setIndexRoute(-1) - tree, ok := c.App().treeStack[i][c.getTreePath()] + tree, ok := app.treeStack[i][c.getTreePath()] if !ok { - tree = c.App().treeStack[i][""] + tree = app.treeStack[i][""] } // Get stack length lenr := len(tree) - 1 @@ -146,8 +146,9 @@ func (app *App) methodExist(c *DefaultCtx) bool { } // Scan stack if other methods match the request -func (app *App) methodExistCustom(c CustomCtx) bool { +func (app *App[TCtx]) methodExistCustom(c CustomCtx[TCtx]) bool { var exists bool + methods := app.config.RequestMethods for i := 0; i < len(methods); i++ { // Skip original method @@ -157,9 +158,9 @@ func (app *App) methodExistCustom(c CustomCtx) bool { // Reset stack index c.setIndexRoute(-1) - tree, ok := c.App().treeStack[i][c.getTreePath()] + tree, ok := app.treeStack[i][c.getTreePath()] if !ok { - tree = c.App().treeStack[i][""] + tree = app.treeStack[i][""] } // Get stack length lenr := len(tree) - 1 @@ -190,9 +191,9 @@ func (app *App) methodExistCustom(c CustomCtx) bool { } // uniqueRouteStack drop all not unique routes from the slice -func uniqueRouteStack(stack []*Route) []*Route { - var unique []*Route - m := make(map[*Route]struct{}) +func uniqueRouteStack[TCtx CtxGeneric[TCtx]](stack []*Route[TCtx]) []*Route[TCtx] { + var unique []*Route[TCtx] + m := make(map[*Route[TCtx]]struct{}) for _, v := range stack { if _, ok := m[v]; !ok { m[v] = struct{}{} @@ -544,7 +545,7 @@ func matchEtag(s, etag string) bool { return false } -func (app *App) isEtagStale(etag string, noneMatchBytes []byte) bool { +func (app *App[TCtx]) isEtagStale(etag string, noneMatchBytes []byte) bool { var start, end int // Adapted from: @@ -654,7 +655,7 @@ func getBytesImmutable(s string) []byte { } // HTTP methods and their unique INTs -func (app *App) methodInt(s string) int { +func (app *App[TCtx]) methodInt(s string) int { // For better performance if len(app.configured.RequestMethods) == 0 { // TODO: Use iota instead diff --git a/hooks.go b/hooks.go index 314717d04b..6cc75da813 100644 --- a/hooks.go +++ b/hooks.go @@ -6,32 +6,32 @@ import ( // OnRouteHandler Handlers define a function to create hooks for Fiber. type ( - OnRouteHandler = func(Route) error - OnNameHandler = OnRouteHandler - OnGroupHandler = func(Group) error - OnGroupNameHandler = OnGroupHandler - OnListenHandler = func(ListenData) error - OnPreShutdownHandler = func() error - OnPostShutdownHandler = func(error) error - OnForkHandler = func(int) error - OnMountHandler = func(*App) error + OnRouteHandler [TCtx CtxGeneric[TCtx]] = func(Route[TCtx]) error + OnNameHandler [TCtx CtxGeneric[TCtx]] = OnRouteHandler[TCtx] + OnGroupHandler [TCtx CtxGeneric[TCtx]] = func(Group[TCtx]) error + OnGroupNameHandler [TCtx CtxGeneric[TCtx]] = OnGroupHandler[TCtx] + OnListenHandler = func(ListenData) error + OnPreShutdownHandler = func() error + OnPostShutdownHandler = func(error)error + OnForkHandler = func(int) error + OnMountHandler[TCtx CtxGeneric[TCtx]] = func(*App[TCtx]) error ) // Hooks is a struct to use it with App. -type Hooks struct { +type Hooks[TCtx CtxGeneric[TCtx]] struct { // Embed app - app *App + app *App[TCtx] // Hooks - onRoute []OnRouteHandler - onName []OnNameHandler - onGroup []OnGroupHandler - onGroupName []OnGroupNameHandler + onRoute []OnRouteHandler[TCtx] + onName []OnNameHandler[TCtx] + onGroup []OnGroupHandler[TCtx] + onGroupName []OnGroupNameHandler[TCtx] onListen []OnListenHandler onPreShutdown []OnPreShutdownHandler onPostShutdown []OnPostShutdownHandler onFork []OnForkHandler - onMount []OnMountHandler + onMount []OnMountHandler[TCtx] } // ListenData is a struct to use it with OnListenHandler @@ -41,24 +41,24 @@ type ListenData struct { TLS bool } -func newHooks(app *App) *Hooks { - return &Hooks{ +func newHooks[TCtx CtxGeneric[TCtx]](app *App[TCtx]) *Hooks[TCtx] { + return &Hooks[TCtx]{ app: app, - onRoute: make([]OnRouteHandler, 0), - onGroup: make([]OnGroupHandler, 0), - onGroupName: make([]OnGroupNameHandler, 0), - onName: make([]OnNameHandler, 0), + onRoute: make([]OnRouteHandler[TCtx], 0), + onGroup: make([]OnGroupHandler[TCtx], 0), + onGroupName: make([]OnGroupNameHandler[TCtx], 0), + onName: make([]OnNameHandler[TCtx], 0), onListen: make([]OnListenHandler, 0), onPreShutdown: make([]OnPreShutdownHandler, 0), onPostShutdown: make([]OnPostShutdownHandler, 0), onFork: make([]OnForkHandler, 0), - onMount: make([]OnMountHandler, 0), + onMount: make([]OnMountHandler[TCtx], 0), } } // OnRoute is a hook to execute user functions on each route registration. // Also you can get route properties by route parameter. -func (h *Hooks) OnRoute(handler ...OnRouteHandler) { +func (h *Hooks[TCtx]) OnRoute(handler ...OnRouteHandler[TCtx]) { h.app.mutex.Lock() h.onRoute = append(h.onRoute, handler...) h.app.mutex.Unlock() @@ -68,7 +68,7 @@ func (h *Hooks) OnRoute(handler ...OnRouteHandler) { // Also you can get route properties by route parameter. // // WARN: OnName only works with naming routes, not groups. -func (h *Hooks) OnName(handler ...OnNameHandler) { +func (h *Hooks[TCtx]) OnName(handler ...OnNameHandler[TCtx]) { h.app.mutex.Lock() h.onName = append(h.onName, handler...) h.app.mutex.Unlock() @@ -76,7 +76,7 @@ func (h *Hooks) OnName(handler ...OnNameHandler) { // OnGroup is a hook to execute user functions on each group registration. // Also you can get group properties by group parameter. -func (h *Hooks) OnGroup(handler ...OnGroupHandler) { +func (h *Hooks[TCtx]) OnGroup(handler ...OnGroupHandler[TCtx]) { h.app.mutex.Lock() h.onGroup = append(h.onGroup, handler...) h.app.mutex.Unlock() @@ -86,14 +86,14 @@ func (h *Hooks) OnGroup(handler ...OnGroupHandler) { // Also you can get group properties by group parameter. // // WARN: OnGroupName only works with naming groups, not routes. -func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) { +func (h *Hooks[TCtx]) OnGroupName(handler ...OnGroupNameHandler[TCtx]) { h.app.mutex.Lock() h.onGroupName = append(h.onGroupName, handler...) h.app.mutex.Unlock() } // OnListen is a hook to execute user functions on Listen, ListenTLS, Listener. -func (h *Hooks) OnListen(handler ...OnListenHandler) { +func (h *Hooks[TCtx]) OnListen(handler ...OnListenHandler) { h.app.mutex.Lock() h.onListen = append(h.onListen, handler...) h.app.mutex.Unlock() @@ -107,14 +107,14 @@ func (h *Hooks) OnPreShutdown(handler ...OnPreShutdownHandler) { } // OnPostShutdown is a hook to execute user functions after Shutdown. -func (h *Hooks) OnPostShutdown(handler ...OnPostShutdownHandler) { +func (h *Hooks[TCtx]) OnPostShutdown(handler ...OnPostShutdownHandler) { h.app.mutex.Lock() h.onPostShutdown = append(h.onPostShutdown, handler...) h.app.mutex.Unlock() } // OnFork is a hook to execute user function after fork process. -func (h *Hooks) OnFork(handler ...OnForkHandler) { +func (h *Hooks[TCtx]) OnFork(handler ...OnForkHandler) { h.app.mutex.Lock() h.onFork = append(h.onFork, handler...) h.app.mutex.Unlock() @@ -123,13 +123,13 @@ func (h *Hooks) OnFork(handler ...OnForkHandler) { // OnMount is a hook to execute user function after mounting process. // The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter. // It works for app and group mounting. -func (h *Hooks) OnMount(handler ...OnMountHandler) { +func (h *Hooks[TCtx]) OnMount(handler ...OnMountHandler[TCtx]) { h.app.mutex.Lock() h.onMount = append(h.onMount, handler...) h.app.mutex.Unlock() } -func (h *Hooks) executeOnRouteHooks(route Route) error { +func (h *Hooks[TCtx]) executeOnRouteHooks(route Route[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { route.path = h.app.mountFields.mountPath + route.path @@ -145,7 +145,7 @@ func (h *Hooks) executeOnRouteHooks(route Route) error { return nil } -func (h *Hooks) executeOnNameHooks(route Route) error { +func (h *Hooks[TCtx]) executeOnNameHooks(route Route[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { route.path = h.app.mountFields.mountPath + route.path @@ -161,7 +161,7 @@ func (h *Hooks) executeOnNameHooks(route Route) error { return nil } -func (h *Hooks) executeOnGroupHooks(group Group) error { +func (h *Hooks[TCtx]) executeOnGroupHooks(group Group[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { group.Prefix = h.app.mountFields.mountPath + group.Prefix @@ -176,7 +176,7 @@ func (h *Hooks) executeOnGroupHooks(group Group) error { return nil } -func (h *Hooks) executeOnGroupNameHooks(group Group) error { +func (h *Hooks[TCtx]) executeOnGroupNameHooks(group Group[TCtx]) error { // Check mounting if h.app.mountFields.mountPath != "" { group.Prefix = h.app.mountFields.mountPath + group.Prefix @@ -191,7 +191,7 @@ func (h *Hooks) executeOnGroupNameHooks(group Group) error { return nil } -func (h *Hooks) executeOnListenHooks(listenData ListenData) error { +func (h *Hooks[TCtx]) executeOnListenHooks(listenData ListenData) error { for _, v := range h.onListen { if err := v(listenData); err != nil { return err @@ -201,7 +201,7 @@ func (h *Hooks) executeOnListenHooks(listenData ListenData) error { return nil } -func (h *Hooks) executeOnPreShutdownHooks() { +func (h *Hooks[TCtx]) executeOnPreShutdownHooks() { for _, v := range h.onPreShutdown { if err := v(); err != nil { log.Errorf("failed to call pre shutdown hook: %v", err) @@ -217,7 +217,7 @@ func (h *Hooks) executeOnPostShutdownHooks(err error) { } } -func (h *Hooks) executeOnForkHooks(pid int) { +func (h *Hooks[TCtx]) executeOnForkHooks(pid int) { for _, v := range h.onFork { if err := v(pid); err != nil { log.Errorf("failed to call fork hook: %v", err) @@ -225,7 +225,7 @@ func (h *Hooks) executeOnForkHooks(pid int) { } } -func (h *Hooks) executeOnMountHooks(app *App) error { +func (h *Hooks[TCtx]) executeOnMountHooks(app *App[TCtx]) error { for _, v := range h.onMount { if err := v(app); err != nil { return err diff --git a/listen.go b/listen.go index f33c9dafda..f86ac3af27 100644 --- a/listen.go +++ b/listen.go @@ -39,7 +39,7 @@ const ( ) // ListenConfig is a struct to customize startup of Fiber. -type ListenConfig struct { +type ListenConfig[TCtx CtxGeneric[TCtx]] struct { // GracefulContext is a field to shutdown Fiber by given context gracefully. // // Default: nil @@ -58,7 +58,7 @@ type ListenConfig struct { // BeforeServeFunc allows customizing and accessing fiber app before serving the app. // // Default: nil - BeforeServeFunc func(app *App) error `json:"before_serve_func"` + BeforeServeFunc func(app *App[TCtx]) error `json:"before_serve_func"` // AutoCertManager manages TLS certificates automatically using the ACME protocol, // Enables integration with Let's Encrypt or other ACME-compatible providers. @@ -120,9 +120,9 @@ type ListenConfig struct { } // listenConfigDefault is a function to set default values of ListenConfig. -func listenConfigDefault(config ...ListenConfig) ListenConfig { +func listenConfigDefault[TCtx CtxGeneric[TCtx]](config ...ListenConfig[TCtx]) ListenConfig[TCtx] { if len(config) < 1 { - return ListenConfig{ + return ListenConfig[TCtx]{ TLSMinVersion: tls.VersionTLS12, ListenerNetwork: NetworkTCP4, ShutdownTimeout: 10 * time.Second, @@ -151,8 +151,8 @@ func listenConfigDefault(config ...ListenConfig) ListenConfig { // app.Listen(":8080") // app.Listen("127.0.0.1:8080") // app.Listen(":8080", ListenConfig{EnablePrefork: true}) -func (app *App) Listen(addr string, config ...ListenConfig) error { - cfg := listenConfigDefault(config...) +func (app *App[TCtx]) Listen(addr string, config ...ListenConfig[TCtx]) error { + cfg := listenConfigDefault[TCtx](config...) // Configure TLS var tlsConfig *tls.Config @@ -238,7 +238,7 @@ func (app *App) Listen(addr string, config ...ListenConfig) error { // Listener serves HTTP requests from the given listener. // You should enter custom ListenConfig to customize startup. (prefork, startup message, graceful shutdown...) -func (app *App) Listener(ln net.Listener, config ...ListenConfig) error { +func (app *App[TCtx]) Listener(ln net.Listener, config ...ListenConfig[TCtx]) error { cfg := listenConfigDefault(config...) // Graceful shutdown @@ -274,7 +274,7 @@ func (app *App) Listener(ln net.Listener, config ...ListenConfig) error { } // Create listener function. -func (*App) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) (net.Listener, error) { +func (*App[TCtx]) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig[TCtx]) (net.Listener, error) { var listener net.Listener var err error @@ -297,7 +297,7 @@ func (*App) createListener(addr string, tlsConfig *tls.Config, cfg ListenConfig) return listener, nil } -func (app *App) printMessages(cfg ListenConfig, ln net.Listener) { +func (app *App[TCtx]) printMessages(cfg ListenConfig[TCtx], ln net.Listener) { // Print startup message if !cfg.DisableStartupMessage { app.startupMessage(ln.Addr().String(), getTLSConfig(ln) != nil, "", cfg) @@ -310,7 +310,7 @@ func (app *App) printMessages(cfg ListenConfig, ln net.Listener) { } // prepareListenData create an slice of ListenData -func (*App) prepareListenData(addr string, isTLS bool, cfg ListenConfig) ListenData { //revive:disable-line:flag-parameter // Accepting a bool param named isTLS if fine here +func (*App[TCtx]) prepareListenData(addr string, isTLS bool, cfg ListenConfig[TCtx]) ListenData { //revive:disable-line:flag-parameter // Accepting a bool param named isTLS if fine here host, port := parseAddr(addr) if host == "" { if cfg.ListenerNetwork == NetworkTCP6 { @@ -328,7 +328,7 @@ func (*App) prepareListenData(addr string, isTLS bool, cfg ListenConfig) ListenD } // startupMessage prepares the startup message with the handler number, port, address and other information -func (app *App) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig) { //nolint: revive // Accepting a bool param named isTLS if fine here +func (app *App[TCtx]) startupMessage(addr string, isTLS bool, pids string, cfg ListenConfig[TCtx]) { //nolint: revive // Accepting a bool param named isTLS if fine here // ignore child processes if IsChild() { return @@ -439,7 +439,7 @@ func (app *App) startupMessage(addr string, isTLS bool, pids string, cfg ListenC // method | path | name | handlers // GET | / | routeName | github.com/gofiber/fiber/v3.emptyHandler // HEAD | / | | github.com/gofiber/fiber/v3.emptyHandler -func (app *App) printRoutesMessage() { +func (app *App[TCtx]) printRoutesMessage() { // ignore child processes if IsChild() { return @@ -485,7 +485,7 @@ func (app *App) printRoutesMessage() { } // shutdown goroutine -func (app *App) gracefulShutdown(ctx context.Context, cfg ListenConfig) { +func (app *App[TCtx]) gracefulShutdown(ctx context.Context, cfg ListenConfig[TCtx]) { <-ctx.Done() var err error diff --git a/listen_test.go b/listen_test.go index 1a5bd77fa1..8d943b3f0d 100644 --- a/listen_test.go +++ b/listen_test.go @@ -399,9 +399,9 @@ func Test_Listen_BeforeServeFunc(t *testing.T) { }() wantErr := errors.New("test") - require.ErrorIs(t, app.Listen(":0", ListenConfig{ + require.ErrorIs(t, app.Listen(":0", ListenConfig[*DefaultCtx]{ DisableStartupMessage: true, - BeforeServeFunc: func(fiber *App) error { + BeforeServeFunc: func(fiber *App[*DefaultCtx]) error { handlers = fiber.HandlersCount() return wantErr diff --git a/mount.go b/mount.go index f05ec82d1d..ea036487a7 100644 --- a/mount.go +++ b/mount.go @@ -13,9 +13,9 @@ import ( ) // Put fields related to mounting. -type mountFields struct { +type mountFields[TCtx CtxGeneric[TCtx]] struct { // Mounted and main apps - appList map[string]*App + appList map[string]*App[TCtx] // Prefix of app if it was mounted mountPath string // Ordered keys of apps (sorted by key length for Render) @@ -27,9 +27,9 @@ type mountFields struct { } // Create empty mountFields instance -func newMountFields(app *App) *mountFields { - return &mountFields{ - appList: map[string]*App{"": app}, +func newMountFields[TCtx CtxGeneric[TCtx]](app *App[TCtx]) *mountFields[TCtx] { + return &mountFields[TCtx]{ + appList: map[string]*App[TCtx]{"": app}, appListKeys: make([]string, 0), } } @@ -39,7 +39,7 @@ func newMountFields(app *App) *mountFields { // compose them as a single service using Mount. The fiber's error handler and // any of the fiber's sub apps are added to the application's error handlers // to be invoked on errors that happen within the prefix route. -func (app *App) mount(prefix string, subApp *App) Router { +func (app *App[TCtx]) mount(prefix string, subApp *App[TCtx]) Router[TCtx] { prefix = utils.TrimRight(prefix, '/') if prefix == "" { prefix = "/" @@ -54,7 +54,7 @@ func (app *App) mount(prefix string, subApp *App) Router { } // register mounted group - mountGroup := &Group{Prefix: prefix, app: subApp} + mountGroup := &Group[TCtx]{Prefix: prefix, app: subApp} app.register([]string{methodUse}, prefix, mountGroup) // Execute onMount hooks @@ -68,7 +68,7 @@ func (app *App) mount(prefix string, subApp *App) Router { // Mount attaches another app instance as a sub-router along a routing path. // It's very useful to split up a large API as many independent routers and // compose them as a single service using Mount. -func (grp *Group) mount(prefix string, subApp *App) Router { +func (grp *Group[TCtx]) mount(prefix string, subApp *App[TCtx]) Router[TCtx] { groupPath := getGroupPath(grp.Prefix, prefix) groupPath = utils.TrimRight(groupPath, '/') if groupPath == "" { @@ -84,7 +84,7 @@ func (grp *Group) mount(prefix string, subApp *App) Router { } // register mounted group - mountGroup := &Group{Prefix: groupPath, app: subApp} + mountGroup := &Group[TCtx]{Prefix: groupPath, app: subApp} grp.app.register([]string{methodUse}, groupPath, mountGroup) // Execute onMount hooks @@ -96,17 +96,17 @@ func (grp *Group) mount(prefix string, subApp *App) Router { } // The MountPath property contains one or more path patterns on which a sub-app was mounted. -func (app *App) MountPath() string { +func (app *App[TCtx]) MountPath() string { return app.mountFields.mountPath } // hasMountedApps Checks if there are any mounted apps in the current application. -func (app *App) hasMountedApps() bool { +func (app *App[TCtx]) hasMountedApps() bool { return len(app.mountFields.appList) > 1 } // mountStartupProcess Handles the startup process of mounted apps by appending sub-app routes, generating app list keys, and processing sub-app routes. -func (app *App) mountStartupProcess() { +func (app *App[TCtx]) mountStartupProcess() { if app.hasMountedApps() { // add routes of sub-apps app.mountFields.subAppsProcessed.Do(func() { @@ -121,7 +121,7 @@ func (app *App) mountStartupProcess() { } // generateAppListKeys generates app list keys for Render, should work after appendSubAppLists -func (app *App) generateAppListKeys() { +func (app *App[TCtx]) generateAppListKeys() { for key := range app.mountFields.appList { app.mountFields.appListKeys = append(app.mountFields.appListKeys, key) } @@ -132,7 +132,7 @@ func (app *App) generateAppListKeys() { } // appendSubAppLists supports nested for sub apps -func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { +func (app *App[TCtx]) appendSubAppLists(appList map[string]*App[TCtx], parent ...string) { // Optimize: Cache parent prefix parentPrefix := "" if len(parent) > 0 { @@ -161,7 +161,7 @@ func (app *App) appendSubAppLists(appList map[string]*App, parent ...string) { } // processSubAppsRoutes adds routes of sub-apps recursively when the server is started -func (app *App) processSubAppsRoutes() { +func (app *App[TCtx]) processSubAppsRoutes() { for prefix, subApp := range app.mountFields.appList { // skip real app if prefix == "" { @@ -194,7 +194,7 @@ func (app *App) processSubAppsRoutes() { } // Create a slice to hold the sub-app's routes - subRoutes := make([]*Route, len(route.group.app.stack[m])) + subRoutes := make([]*Route[TCtx], len(route.group.app.stack[m])) // Iterate over the sub-app's routes for j, subAppRoute := range route.group.app.stack[m] { @@ -209,7 +209,7 @@ func (app *App) processSubAppsRoutes() { } // Insert the sub-app's routes into the parent app's stack - newStack := make([]*Route, len(app.stack[m])+len(subRoutes)-1) + newStack := make([]*Route[TCtx], len(app.stack[m])+len(subRoutes)-1) copy(newStack[:i], app.stack[m][:i]) copy(newStack[i:i+len(subRoutes)], subRoutes) copy(newStack[i+len(subRoutes):], app.stack[m][i+1:]) diff --git a/prefork.go b/prefork.go index 745ed30627..3e05352d6c 100644 --- a/prefork.go +++ b/prefork.go @@ -35,7 +35,7 @@ func IsChild() bool { } // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature -func (app *App) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig) error { +func (app *App[TCtx]) prefork(addr string, tlsConfig *tls.Config, cfg ListenConfig[TCtx]) error { var ln net.Listener var err error diff --git a/register.go b/register.go index c7e8a12a8b..7c04040304 100644 --- a/register.go +++ b/register.go @@ -5,28 +5,28 @@ package fiber // Register defines all router handle interface generate by Route(). -type Register interface { - All(handler Handler, handlers ...Handler) Register - Get(handler Handler, handlers ...Handler) Register - Head(handler Handler, handlers ...Handler) Register - Post(handler Handler, handlers ...Handler) Register - Put(handler Handler, handlers ...Handler) Register - Delete(handler Handler, handlers ...Handler) Register - Connect(handler Handler, handlers ...Handler) Register - Options(handler Handler, handlers ...Handler) Register - Trace(handler Handler, handlers ...Handler) Register - Patch(handler Handler, handlers ...Handler) Register - - Add(methods []string, handler Handler, handlers ...Handler) Register - - Route(path string) Register +type Register[TCtx CtxGeneric[TCtx]] interface { + All(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Get(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Head(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Post(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Put(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Delete(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Connect(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Options(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Trace(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + Patch(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + + Add(methods []string, handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] + + Route(path string) Register[TCtx] } -var _ (Register) = (*Registering)(nil) +var _ Register[*DefaultCtx] = (*Registering[*DefaultCtx])(nil) // Registering struct -type Registering struct { - app *App +type Registering[TCtx CtxGeneric[TCtx]] struct { + app *App[TCtx] path string } @@ -45,76 +45,76 @@ type Registering struct { // }) // // This method will match all HTTP verbs: GET, POST, PUT, HEAD etc... -func (r *Registering) All(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) All(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { r.app.register([]string{methodUse}, r.path, nil, append([]Handler{handler}, handlers...)...) return r } // Get registers a route for GET methods that requests a representation // of the specified resource. Requests using GET should only retrieve data. -func (r *Registering) Get(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Get(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { r.app.Add([]string{MethodGet}, r.path, handler, handlers...) return r } // Head registers a route for HEAD methods that asks for a response identical // to that of a GET request, but without the response body. -func (r *Registering) Head(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Head(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodHead}, handler, handlers...) } // Post registers a route for POST methods that is used to submit an entity to the // specified resource, often causing a change in state or side effects on the server. -func (r *Registering) Post(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Post(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPost}, handler, handlers...) } // Put registers a route for PUT methods that replaces all current representations // of the target resource with the request payload. -func (r *Registering) Put(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Put(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPut}, handler, handlers...) } // Delete registers a route for DELETE methods that deletes the specified resource. -func (r *Registering) Delete(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Delete(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodDelete}, handler, handlers...) } // Connect registers a route for CONNECT methods that establishes a tunnel to the // server identified by the target resource. -func (r *Registering) Connect(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Connect(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodConnect}, handler, handlers...) } // Options registers a route for OPTIONS methods that is used to describe the // communication options for the target resource. -func (r *Registering) Options(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Options(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodOptions}, handler, handlers...) } // Trace registers a route for TRACE methods that performs a message loop-back // test along the r.Path to the target resource. -func (r *Registering) Trace(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Trace(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodTrace}, handler, handlers...) } // Patch registers a route for PATCH methods that is used to apply partial // modifications to a resource. -func (r *Registering) Patch(handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Patch(handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { return r.Add([]string{MethodPatch}, handler, handlers...) } // Add allows you to specify multiple HTTP methods to register a route. -func (r *Registering) Add(methods []string, handler Handler, handlers ...Handler) Register { +func (r *Registering[TCtx]) Add(methods []string, handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx] { r.app.register(methods, r.path, nil, append([]Handler{handler}, handlers...)...) return r } // Route returns a new Register instance whose route path takes // the path in the current instance as its prefix. -func (r *Registering) Route(path string) Register { +func (r *Registering[TCtx]) Route(path string) Register[TCtx] { // Create new group - route := &Registering{app: r.app, path: getGroupPath(r.path, path)} + route := &Registering[TCtx]{app: r.app, path: getGroupPath(r.path, path)} return route } diff --git a/router.go b/router.go index a14d2edd74..664dc3ccb0 100644 --- a/router.go +++ b/router.go @@ -6,9 +6,7 @@ package fiber import ( "bytes" - "errors" "fmt" - "html" "sort" "sync/atomic" @@ -17,33 +15,33 @@ import ( ) // Router defines all router handle interface, including app and group router. -type Router interface { - Use(args ...any) Router +type Router[TCtx CtxGeneric[TCtx]] interface { + Use(args ...any) Router[TCtx] - Get(path string, handler Handler, handlers ...Handler) Router - Head(path string, handler Handler, handlers ...Handler) Router - Post(path string, handler Handler, handlers ...Handler) Router - Put(path string, handler Handler, handlers ...Handler) Router - Delete(path string, handler Handler, handlers ...Handler) Router - Connect(path string, handler Handler, handlers ...Handler) Router - Options(path string, handler Handler, handlers ...Handler) Router - Trace(path string, handler Handler, handlers ...Handler) Router - Patch(path string, handler Handler, handlers ...Handler) Router + Get(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Head(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Post(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Put(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Delete(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Connect(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Options(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Trace(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + Patch(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] - Add(methods []string, path string, handler Handler, handlers ...Handler) Router - All(path string, handler Handler, handlers ...Handler) Router + Add(methods []string, path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] + All(path string, handler Handler[TCtx], handlers ...Handler[TCtx]) Router[TCtx] - Group(prefix string, handlers ...Handler) Router + Group(prefix string, handlers ...Handler[TCtx]) Router[TCtx] - Route(path string) Register + Route(path string) Register[TCtx] - Name(name string) Router + Name(name string) Router[TCtx] } // Route is a struct that holds all metadata for each registered handler. -type Route struct { +type Route[TCtx CtxGeneric[TCtx]] struct { // ### important: always keep in sync with the copy method "app.copyRoute" ### - group *Group // Group instance. used for routes in groups + group *Group[TCtx] // Group instance. used for routes in groups path string // Prettified path @@ -51,10 +49,10 @@ type Route struct { Method string `json:"method"` // HTTP method Name string `json:"name"` // Route's name //nolint:revive // Having both a Path (uppercase) and a path (lowercase) is fine - Path string `json:"path"` // Original registered route path - Params []string `json:"params"` // Case-sensitive param keys - Handlers []Handler `json:"-"` // Ctx handlers - routeParser routeParser // Parameter parser + Path string `json:"path"` // Original registered route path + Params []string `json:"params"` // Case-sensitive param keys + Handlers []Handler[TCtx] `json:"-"` // Ctx handlers + routeParser routeParser // Parameter parser // Data for routing pos uint32 // Position in stack -> important for the sort of the matched routes use bool // USE matches path prefixes @@ -63,7 +61,7 @@ type Route struct { root bool // Path equals '/' } -func (r *Route) match(detectionPath, path string, params *[maxParams]string) bool { +func (r *Route[TCtx]) match(detectionPath, path string, params *[maxParams]string) bool { // root detectionPath check if r.root && len(detectionPath) == 1 && detectionPath[0] == '/' { return true @@ -108,7 +106,7 @@ func (r *Route) match(detectionPath, path string, params *[maxParams]string) boo return false } -func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // bool param might be useful for testing +func (app *App[TCtx]) next(c TCtx) (bool, error) { //nolint: unparam // bool param might be useful for testing // Get stack length tree, ok := app.treeStack[c.getMethodINT()][c.getTreePath()] if !ok { @@ -156,98 +154,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // boo return false, err } -func (app *App) next(c *DefaultCtx) (bool, error) { - // Get stack length - tree, ok := app.treeStack[c.methodINT][c.treePath] - if !ok { - tree = app.treeStack[c.methodINT][""] - } - lenTree := len(tree) - 1 - - // Loop over the route stack starting from previous index - for c.indexRoute < lenTree { - // Increment route index - c.indexRoute++ - - // Get *Route - route := tree[c.indexRoute] - - var match bool - var err error - // skip for mounted apps - if route.mount { - continue - } - - // Check if it matches the request path - match = route.match(c.detectionPath, c.path, &c.values) - if !match { - // No match, next route - continue - } - // Pass route reference and param values - c.route = route - - // Non use handler matched - if !c.matched && !route.use { - c.matched = true - } - - // Execute first handler of route - c.indexHandler = 0 - if len(route.Handlers) > 0 { - err = route.Handlers[0](c) - } - return match, err // Stop scanning the stack - } - - // If c.Next() does not match, return 404 - 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 - err = ErrMethodNotAllowed - } - return false, err -} - -func (app *App) defaultRequestHandler(rctx *fasthttp.RequestCtx) { +func (app *App[TCtx]) requestHandler(rctx *fasthttp.RequestCtx) { // Acquire DefaultCtx from the pool - ctx, ok := app.AcquireCtx(rctx).(*DefaultCtx) - if !ok { - panic(errors.New("requestHandler: failed to type-assert to *DefaultCtx")) - } - - defer app.ReleaseCtx(ctx) - - // Check if the HTTP method is valid - if ctx.methodINT == -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.next(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? - } -} - -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")) - } + ctx := app.AcquireCtx(rctx) defer app.ReleaseCtx(ctx) @@ -264,16 +173,16 @@ func (app *App) customRequestHandler(rctx *fasthttp.RequestCtx) { } // Attempt to match a route and execute the chain - _, err := app.nextCustom(ctx) + _, err := app.next(ctx) if err != nil { - if catch := ctx.App().ErrorHandler(ctx, err); catch != nil { + if catch := app.ErrorHandler(ctx, err); catch != nil { _ = ctx.SendStatus(StatusInternalServerError) //nolint:errcheck // Always return nil } // TODO: Do we need to return here? } } -func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { +func (app *App[TCtx]) addPrefixToRoute(prefix string, route *Route[TCtx]) *Route[TCtx] { prefixedPath := getGroupPath(prefix, route.Path) prettyPath := prefixedPath // Case-sensitive routing, all to lowercase @@ -294,8 +203,8 @@ func (app *App) addPrefixToRoute(prefix string, route *Route) *Route { return route } -func (*App) copyRoute(route *Route) *Route { - return &Route{ +func (*App[TCtx]) copyRoute(route *Route[TCtx]) *Route[TCtx] { + return &Route[TCtx]{ // Router booleans use: route.use, mount: route.mount, @@ -318,7 +227,7 @@ func (*App) copyRoute(route *Route) *Route { } } -func (app *App) register(methods []string, pathRaw string, group *Group, handlers ...Handler) { +func (app *App[TCtx]) register(methods []string, pathRaw string, group *Group[TCtx], handlers ...Handler[TCtx]) { // A regular route requires at least one ctx handler if len(handlers) == 0 && group == nil { panic(fmt.Sprintf("missing handler/middleware in route: %s\n", pathRaw)) @@ -361,7 +270,7 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler isStar := pathClean == "/*" isRoot := pathClean == "/" - route := Route{ + route := Route[TCtx]{ use: isUse, mount: isMount, star: isStar, @@ -395,7 +304,7 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler } } -func (app *App) addRoute(method string, route *Route, isMounted ...bool) { +func (app *App[TCtx]) addRoute(method string, route *Route[TCtx], isMounted ...bool) { app.mutex.Lock() defer app.mutex.Unlock() @@ -431,7 +340,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) { } } -// BuildTree rebuilds the prefix tree from the previously registered routes. +// RebuildTree BuildTree rebuilds the prefix tree from the previously registered routes. // This method is useful when you want to register routes dynamically after the app has started. // It is not recommended to use this method on production environments because rebuilding // the tree is performance-intensive and not thread-safe in runtime. Since building the tree @@ -439,7 +348,7 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) { // routeTree is being safely changed, as it would add a great deal of overhead in the request. // Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in: // https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283 -func (app *App) RebuildTree() *App { +func (app *App[TCtx]) RebuildTree() *App[TCtx] { app.mutex.Lock() defer app.mutex.Unlock() @@ -447,14 +356,14 @@ func (app *App) RebuildTree() *App { } // buildTree build the prefix tree from the previously registered routes -func (app *App) buildTree() *App { +func (app *App[TCtx]) buildTree() *App[TCtx] { if !app.routesRefreshed { return app } // loop all the methods and stacks and create the prefix tree for m := range app.config.RequestMethods { - tsMap := make(map[string][]*Route) + tsMap := make(map[string][]*Route[TCtx]) for _, route := range app.stack[m] { treePath := "" if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= 3 { @@ -472,7 +381,7 @@ func (app *App) buildTree() *App { for treePart := range tsMap { if treePart != "" { // merge global tree routes in current tree stack - tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[""]...)) + tsMap[treePart] = uniqueRouteStack[TCtx](append(tsMap[treePart], tsMap[""]...)) } // sort tree slices with the positions slc := tsMap[treePart] diff --git a/router_test.go b/router_test.go index 0ac0c212ca..36fe242060 100644 --- a/router_test.go +++ b/router_test.go @@ -442,7 +442,7 @@ func Test_App_Rebuild_Tree(t *testing.T) { ///////////////// BENCHMARKS ///////////////// ////////////////////////////////////////////// -func registerDummyRoutes(app *App) { +func registerDummyRoutes(app *App[TCtx]) { h := func(_ Ctx) error { return nil }