pull/3296/merge
RW 2025-03-31 09:24:20 +02:00 committed by GitHub
commit 3a86aaf4f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 821 additions and 802 deletions

198
app.go
View File

@ -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[int][]*Route
treeStack []map[int][]*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.
@ -482,7 +482,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) {
@ -502,14 +502,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{},
}
@ -522,7 +562,7 @@ func New(config ...Config) *App {
}
// Define hooks
app.hooks = newHooks(app)
app.hooks = newHooks[TCtx](app)
// Define mountFields
app.mountFields = newMountFields(app)
@ -561,7 +601,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 {
@ -592,21 +632,18 @@ func New(config ...Config) *App {
}
// Create router stack
app.stack = make([][]*Route, len(app.config.RequestMethods))
app.treeStack = make([]map[int][]*Route, len(app.config.RequestMethods))
app.stack = make([][]*Route[TCtx], len(app.config.RequestMethods))
app.treeStack = make([]map[int][]*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 {
@ -624,29 +661,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
@ -654,7 +681,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()
@ -680,7 +707,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 {
@ -689,12 +716,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]
@ -731,21 +758,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)))
@ -770,66 +797,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...)
}
@ -837,8 +864,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...)
}
@ -851,9 +878,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
}
@ -876,28 +903,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
}
@ -914,7 +938,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())
}
@ -925,7 +949,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)
@ -936,7 +960,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()
@ -955,12 +979,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
}
@ -983,7 +1007,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,
@ -1071,7 +1095,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()
@ -1090,11 +1114,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
@ -1123,9 +1143,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
)
@ -1152,7 +1172,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)
@ -1187,7 +1207,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()
@ -1200,7 +1220,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)
}

19
ctx.go
View File

@ -50,10 +50,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
@ -73,6 +74,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.
@ -224,7 +227,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 {
@ -1744,7 +1749,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
}
@ -1789,7 +1794,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 {
@ -1987,7 +1992,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
}

74
ctx_custom_interface.go Normal file
View File

@ -0,0 +1,74 @@
// ⚡️ 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
ctx := &DefaultCtx{
// Set app reference
app: app,
}
ctx.req = &DefaultReq{ctx: ctx}
ctx.res = &DefaultRes{ctx: ctx}
return ctx
}
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)
}

View File

@ -1,21 +1,350 @@
// ⚡️ 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.
// Make copies or use the Immutable setting to use the value outside the Handler.
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)
// Req returns a convenience type whose API is limited to operations
// on the incoming request.
Req() Req
// Res returns a convenience type whose API is limited to operations
// on the outgoing response.
Res() Res
// 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) 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,47 +356,11 @@ type CustomCtx interface {
setIndexHandler(handler int)
setIndexRoute(route int)
setMatched(matched bool)
setRoute(route *Route)
}
func NewDefaultCtx(app *App) *DefaultCtx {
// return ctx
ctx := &DefaultCtx{
// Set app reference
app: app,
}
ctx.req = &DefaultReq{ctx: ctx}
ctx.res = &DefaultRes{ctx: ctx}
return ctx
}
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
}

View File

@ -1,365 +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)
// Req returns a convenience type whose API is limited to operations
// on the incoming request.
Req() Req
// Res returns a convenience type whose API is limited to operations
// on the outgoing response.
Res() Res
// 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
getTreePathHash() int
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
}

View File

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

View File

@ -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
```
<details>
@ -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"

View File

@ -103,7 +103,7 @@ app.Listen(":8080", fiber.ListenConfig{
| Property | Type | Description | Default |
|-------------------------------------------------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|---------|
| <Reference id="beforeservefunc">BeforeServeFunc</Reference> | `func(app *App) error` | Allows customizing and accessing fiber app before serving the app. | `nil` |
| <Reference id="beforeservefunc">BeforeServeFunc</Reference> | `func(app *App[TCtx]) error` | Allows customizing and accessing fiber app before serving the app. | `nil` |
| <Reference id="certclientfile">CertClientFile</Reference> | `string` | Path of the client certificate. If you want to use mTLS, you must enter this field. | `""` |
| <Reference id="certfile">CertFile</Reference> | `string` | Path of the certificate file. If you want to use TLS, you must enter this field. | `""` |
| <Reference id="certkeyfile">CertKeyFile</Reference> | `string` | Path of the certificate's private key. If you want to use TLS, you must enter this field. | `""` |

View File

@ -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"

View File

@ -76,6 +76,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
@ -84,10 +86,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)
```
<details>

2
go.sum
View File

@ -34,8 +34,6 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

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

View File

@ -94,7 +94,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)
@ -102,7 +102,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
@ -114,9 +114,9 @@ func (app *App) methodExist(c *DefaultCtx) bool {
// Reset stack index
c.setIndexRoute(-1)
tree, ok := c.App().treeStack[i][c.treePathHash]
tree, ok := app.treeStack[i][c.treePathHash]
if !ok {
tree = c.App().treeStack[i][0]
tree = app.treeStack[i][0]
}
// Get stack length
lenr := len(tree) - 1
@ -147,8 +147,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
@ -158,9 +159,9 @@ func (app *App) methodExistCustom(c CustomCtx) bool {
// Reset stack index
c.setIndexRoute(-1)
tree, ok := c.App().treeStack[i][c.getTreePathHash()]
tree, ok := app.treeStack[i][c.getTreePathHash()]
if !ok {
tree = c.App().treeStack[i][0]
tree = app.treeStack[i][0]
}
// Get stack length
lenr := len(tree) - 1
@ -191,9 +192,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{}{}
@ -540,7 +541,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:
@ -650,7 +651,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 {
switch s {

View File

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

View File

@ -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
@ -436,7 +436,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
@ -481,7 +481,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

View File

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

View File

@ -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:])

View File

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

View File

@ -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
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, handlers ...Handler) Register
Add(methods []string, handler Handler[TCtx], handlers ...Handler[TCtx]) Register[TCtx]
Route(path string) Register
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
}

171
router.go
View File

@ -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.getTreePathHash()]
if !ok {
@ -146,7 +144,7 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool
}
// If c.Next() does not match, return 404
err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+c.getPathOriginal())
err := NewError(StatusNotFound, "Cannot "+c.Method()+" "+html.EscapeString(c.getPathOriginal()))
// If no match, scan stack again if other methods match the request
// Moved from app.handler because middleware may break the route chain
@ -156,98 +154,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint:unparam // bool
return false, err
}
func (app *App) next(c *DefaultCtx) (bool, error) {
// Get stack length
tree, ok := app.treeStack[c.methodInt][c.treePathHash]
if !ok {
tree = app.treeStack[c.methodInt][0]
}
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(utils.UnsafeString(c.detectionPath), utils.UnsafeString(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[int][]*Route)
tsMap := make(map[int][]*Route[TCtx])
for _, route := range app.stack[m] {
treePathHash := 0
if len(route.routeParser.segs) > 0 && len(route.routeParser.segs[0].Const) >= maxDetectionPaths {
@ -469,7 +378,7 @@ func (app *App) buildTree() *App {
for treePart := range tsMap {
if treePart != 0 {
// merge global tree routes in current tree stack
tsMap[treePart] = uniqueRouteStack(append(tsMap[treePart], tsMap[0]...))
tsMap[treePart] = uniqueRouteStack[TCtx](append(tsMap[treePart], tsMap[0]...))
}
// sort tree slices with the positions
slc := tsMap[treePart]

View File

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