Skip to content

Add NewWithCustomCtx initialization helper #3476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,10 @@ func New(config ...Config) *App {
// Create Ctx pool
app.pool = sync.Pool{
New: func() any {
return app.newCtx()
if app.newCtxFunc != nil {
return app.newCtxFunc(app)
}
return NewDefaultCtx(app)
},
}

Expand Down Expand Up @@ -623,6 +626,15 @@ func New(config ...Config) *App {
return app
}

// NewWithCustomCtx creates a new Fiber instance and applies the
// provided function to generate a custom context type. It mirrors the behavior
// of calling `New()` followed by `app.setCtxFunc(fn)`.
func NewWithCustomCtx(newCtxFunc func(app *App) CustomCtx, config ...Config) *App {
app := New(config...)
app.setCtxFunc(newCtxFunc)
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) {
if strings.Contains(ipAddress, "/") {
Expand All @@ -642,13 +654,14 @@ 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) {
// setCtxFunc applies the given context factory to the app.
// It is used internally by NewWithCustomCtx. It doesn't allow adding new methods,
// only customizing existing ones.
func (app *App) setCtxFunc(function func(app *App) CustomCtx) {
app.newCtxFunc = function

if app.server != nil {
app.server.Handler = app.customRequestHandler
app.server.Handler = app.requestHandler
}
}

Expand Down Expand Up @@ -902,11 +915,7 @@ func (app *App) Config() Config {
func (app *App) Handler() fasthttp.RequestHandler { //revive:disable-line:confusing-naming // Having both a Handler() (uppercase) and a handler() (lowercase) is fine. TODO: Use nolint:revive directive instead. See https://github.com/golangci/golangci-lint/issues/3476
// 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.
Expand Down Expand Up @@ -1117,11 +1126,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
Expand Down
11 changes: 1 addition & 10 deletions ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,11 +1045,6 @@ func (c *DefaultCtx) Next() error {
}

// Continue handler stack
if c.app.newCtxFunc != nil {
_, err := c.app.nextCustom(c)
return err
}

_, err := c.app.next(c)
return err
}
Expand All @@ -1060,11 +1055,7 @@ func (c *DefaultCtx) RestartRouting() error {
var err error

c.indexRoute = -1
if c.app.newCtxFunc != nil {
_, err = c.app.nextCustom(c)
} else {
_, err = c.app.next(c)
}
_, err = c.app.next(c)
return err
}

Expand Down
18 changes: 3 additions & 15 deletions ctx_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,9 @@ func NewDefaultCtx(app *App) *DefaultCtx {
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)
func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) CustomCtx {
ctx, ok := app.pool.Get().(CustomCtx)
Comment on lines +46 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

LGTM! Context unification looks good, but verify performance impact.

The signature change from Ctx to CustomCtx aligns well with the unified context handling approach. The type assertion ensures type safety at runtime.

However, based on the PR objectives, there are performance concerns (~10ns regression) with this new implementation. Consider monitoring the performance impact of this type assertion in hot paths.


🏁 Script executed:

#!/bin/bash
# Description: Verify that all callers of AcquireCtx have been updated to work with CustomCtx return type
# Expected: All usages should handle CustomCtx properly

echo "Searching for AcquireCtx usage patterns..."
rg -A 3 -B 1 "AcquireCtx" --type go

Length of output: 70365


🏁 Script executed:

#!/bin/bash
# Show the AcquireCtx implementation around the assertion
rg -n "AcquireCtx" -A 5 -B 5 ctx_interface.go

Length of output: 350


Update panic message in AcquireCtx to reflect CustomCtx

  • In ctx_interface.go (line 50), the panic reads
    panic(errors.New("failed to type-assert to Ctx"))
    but the code is asserting to CustomCtx. Please change the message to something like:
    panic(errors.New("failed to type-assert to CustomCtx"))
  • All callers of AcquireCtx already handle the CustomCtx return (verified across router.go, tests, etc.).
  • As noted in the PR description, it may be worth adding or updating micro-benchmarks around hot paths to ensure the extra interface assertion doesn’t introduce unacceptable regressions.
🤖 Prompt for AI Agents
In ctx_interface.go around lines 46 to 47 and specifically at line 50, update
the panic message in the AcquireCtx method to accurately reflect the type
assertion to CustomCtx by changing the error text from "failed to type-assert to
Ctx" to "failed to type-assert to CustomCtx". Additionally, consider adding or
updating micro-benchmarks for AcquireCtx to monitor any performance impact
caused by the type assertion in hot paths.


if !ok {
panic(errors.New("failed to type-assert to Ctx"))
Expand All @@ -67,7 +55,7 @@ func (app *App) AcquireCtx(fctx *fasthttp.RequestCtx) Ctx {
}

// ReleaseCtx releases the ctx back into the pool.
func (app *App) ReleaseCtx(c Ctx) {
func (app *App) ReleaseCtx(c CustomCtx) {
c.release()
app.pool.Put(c)
}
13 changes: 4 additions & 9 deletions ctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ func (c *customCtx) Params(key string, defaultValue ...string) string { //revive
func Test_Ctx_CustomCtx(t *testing.T) {
t.Parallel()

app := New()

app.NewCtxFunc(func(app *App) CustomCtx {
app := NewWithCustomCtx(func(app *App) CustomCtx {
return &customCtx{
DefaultCtx: *NewDefaultCtx(app),
}
Expand All @@ -133,15 +131,12 @@ func Test_Ctx_CustomCtx_and_Method(t *testing.T) {

// Create app with custom request methods
methods := append(DefaultMethods, "JOHN") //nolint:gocritic // We want a new slice here
app := New(Config{
RequestMethods: methods,
})

// Create custom context
app.NewCtxFunc(func(app *App) CustomCtx {
app := NewWithCustomCtx(func(app *App) CustomCtx {
return &customCtx{
DefaultCtx: *NewDefaultCtx(app),
}
}, Config{
RequestMethods: methods,
})

// Add route with custom method
Expand Down
13 changes: 5 additions & 8 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,12 +512,13 @@ func (app *App) Handler() fasthttp.RequestHandler
func (app *App) ErrorHandler(ctx Ctx, err error) error
```

## NewCtxFunc
## NewWithCustomCtx

`NewCtxFunc` allows you to customize the `ctx` struct as needed.
`NewWithCustomCtx` creates a new `*App` and sets the custom context factory
function at construction time.

```go title="Signature"
func (app *App) NewCtxFunc(function func(app *App) CustomCtx)
func NewWithCustomCtx(fn func(app *App) CustomCtx, config ...Config) *App
```
Comment on lines +515 to 522
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Document generic type parameter in signature
The actual implementation of NewWithCustomCtx is generic over the custom context type (with a Ctx constraint), but the brackets around the type parameter are omitted here. Consider showing the full generic signature, for example:

func NewWithCustomCtx[CC Ctx](fn func(app *App) CC, config ...Config) *App
🤖 Prompt for AI Agents
In docs/api/app.md around lines 515 to 522, the function signature for
NewWithCustomCtx is missing the generic type parameter declaration. Update the
signature to include the generic type parameter with its constraint, for
example: func NewWithCustomCtx[CC Ctx](fn func(app *App) CC, config ...Config)
*App, to accurately reflect the implementation.


```go title="Example"
Expand All @@ -533,22 +534,18 @@ type CustomCtx struct {
fiber.DefaultCtx
}

// Custom method
func (c *CustomCtx) Params(key string, defaultValue ...string) string {
return "prefix_" + c.DefaultCtx.Params(key)
}

func main() {
app := fiber.New()

app.NewCtxFunc(func(app *fiber.App) fiber.CustomCtx {
app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx {
return &CustomCtx{
DefaultCtx: *fiber.NewDefaultCtx(app),
}
})
Comment on lines 541 to 546
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix incorrect return type in factory signature
The example’s factory function is declared as func(app *fiber.App) fiber.CustomCtx, but fiber.CustomCtx isn’t a defined type. It should match the local CustomCtx struct or the interface, e.g.:

app := fiber.NewWithCustomCtx(func(app *fiber.App) CustomCtx {
    return &CustomCtx{ DefaultCtx: *fiber.NewDefaultCtx(app) }
})

Or explicitly return the interface type:

func(app *fiber.App) fiber.Ctx { … }
🤖 Prompt for AI Agents
In docs/api/app.md around lines 541 to 546, the factory function passed to
fiber.NewWithCustomCtx has an incorrect return type fiber.CustomCtx, which is
not defined. Change the return type to either the local CustomCtx struct or the
fiber.Ctx interface to match the expected signature. For example, update the
function signature to func(app *fiber.App) *CustomCtx or func(app *fiber.App)
fiber.Ctx and ensure the returned value matches this type.


app.Get("/:id", func(c fiber.Ctx) error {
// Use custom method - output: prefix_123
return c.SendString(c.Params("id"))
})

Expand Down
17 changes: 6 additions & 11 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ We have made several changes to the Fiber app, including:

- **RegisterCustomBinder**: Allows for the registration of custom binders.
- **RegisterCustomConstraint**: Allows for the registration of custom constraints.
- **NewCtxFunc**: Introduces a new context function.
- **NewWithCustomCtx**: Initialize an app with a custom context in one step.
- **State**: Provides a global state for the application, which can be used to store and retrieve data across the application. Check out the [State](./api/state) method for further details.

### Removed Methods
Expand All @@ -90,19 +90,17 @@ Fiber v3 introduces a customizable `Ctx` interface, allowing developers to exten

The idea behind custom `Ctx` classes is to give developers the ability to extend the default context with additional methods and properties tailored to the specific requirements of their application. This allows for better request handling and easier implementation of specific logic.

#### NewCtxFunc
#### NewWithCustomCtx

The `NewCtxFunc` method allows you to customize the `Ctx` struct as needed.
`NewWithCustomCtx` creates the application and sets the custom context factory at initialization time.

```go title="Signature"
func (app *App) NewCtxFunc(function func(app *App) CustomCtx)
func NewWithCustomCtx(fn func(app *App) CustomCtx, config ...Config) *App
```

<details>
<summary>Example</summary>

Here’s an example of how to customize the `Ctx` interface:

```go
package main

Expand All @@ -115,15 +113,12 @@ type CustomCtx struct {
fiber.Ctx
}

// Custom method
func (c *CustomCtx) CustomMethod() string {
return "custom value"
}

func main() {
app := fiber.New()

app.NewCtxFunc(func(app *fiber.App) fiber.Ctx {
app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.Ctx {
return &CustomCtx{
Ctx: *fiber.NewCtx(app),
}
Expand All @@ -138,7 +133,7 @@ func main() {
}
```

In this example, a custom context `CustomCtx` is created with an additional method `CustomMethod`. The `NewCtxFunc` method is used to replace the default context with the custom one.
This example creates a `CustomCtx` with an extra `CustomMethod` and initializes the app with `NewWithCustomCtx`.

</details>

Expand Down
89 changes: 0 additions & 89 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,95 +101,6 @@ func (app *App) quoteString(raw string) string {
return quoted
}

// Scan stack if other methods match the request
func (app *App) methodExist(c *DefaultCtx) bool {
var exists bool

methods := app.config.RequestMethods
for i := 0; i < len(methods); i++ {
// Skip original method
if c.getMethodInt() == i {
continue
}
// Reset stack index
c.setIndexRoute(-1)

tree, ok := c.App().treeStack[i][c.treePathHash]
if !ok {
tree = c.App().treeStack[i][0]
}
// Get stack length
lenr := len(tree) - 1
// Loop over the route stack starting from previous index
for c.getIndexRoute() < lenr {
// Increment route index
c.setIndexRoute(c.getIndexRoute() + 1)
// Get *Route
route := tree[c.getIndexRoute()]
// Skip use routes
if route.use {
continue
}
// Check if it matches the request path
match := route.match(c.getDetectionPath(), c.Path(), c.getValues())
// No match, next route
if match {
// We matched
exists = true
// Add method to Allow header
c.Append(HeaderAllow, methods[i])
// Break stack loop
break
}
}
}
return exists
}

// Scan stack if other methods match the request
func (app *App) methodExistCustom(c CustomCtx) bool {
var exists bool
methods := app.config.RequestMethods
for i := 0; i < len(methods); i++ {
// Skip original method
if c.getMethodInt() == i {
continue
}
// Reset stack index
c.setIndexRoute(-1)

tree, ok := c.App().treeStack[i][c.getTreePathHash()]
if !ok {
tree = c.App().treeStack[i][0]
}
// Get stack length
lenr := len(tree) - 1
// Loop over the route stack starting from previous index
for c.getIndexRoute() < lenr {
// Increment route index
c.setIndexRoute(c.getIndexRoute() + 1)
// Get *Route
route := tree[c.getIndexRoute()]
// Skip use routes
if route.use {
continue
}
// Check if it matches the request path
match := route.match(c.getDetectionPath(), c.Path(), c.getValues())
// No match, next route
if match {
// We matched
exists = true
// Add method to Allow header
c.Append(HeaderAllow, methods[i])
// Break stack loop
break
}
}
}
return exists
}

// uniqueRouteStack drop all not unique routes from the slice
func uniqueRouteStack(stack []*Route) []*Route {
var unique []*Route
Expand Down
Loading
Loading