Skip to content

Commit 2c43a0f

Browse files
🔥 feat: Native support for net/http and fasthttp handlers (#3769)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 9811202 commit 2c43a0f

File tree

13 files changed

+804
-121
lines changed

13 files changed

+804
-121
lines changed

.github/README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,40 @@ We **listen** to our users in [issues](https://github.com/gofiber/fiber/issues),
125125
## ⚠️ Limitations
126126

127127
- Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber v3 has been tested with Go version 1.24 or higher.
128-
- Fiber does not natively expose the `net/http` interfaces. When you need to integrate with that ecosystem, use the [adaptor middleware](https://docs.gofiber.io/next/middleware/adaptor/) to bridge handlers and middlewares between Fiber and `net/http`.
128+
- Fiber automatically adapts common `net/http` handler shapes when you register them on the router, and you can still use the [adaptor middleware](https://docs.gofiber.io/next/middleware/adaptor/) when you need to bridge entire apps or `net/http` middleware.
129129

130130
### net/http compatibility
131131

132-
Fiber can run side by side with the standard library by using the [adaptor middleware](https://docs.gofiber.io/next/middleware/adaptor/). It converts handlers and middlewares in both directions and even lets you mount a Fiber app in a `net/http` server.
132+
Fiber can run side by side with the standard library. The router accepts existing `net/http` handlers directly and even works with native `fasthttp.RequestHandler` callbacks, so you can plug in legacy endpoints without wrapping them manually:
133+
134+
```go
135+
package main
136+
137+
import (
138+
"log"
139+
"net/http"
140+
141+
"github.com/gofiber/fiber/v3"
142+
)
143+
144+
func main() {
145+
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
146+
if _, err := w.Write([]byte("served by net/http")); err != nil {
147+
panic(err)
148+
}
149+
})
150+
151+
app := fiber.New()
152+
app.Get("/", httpHandler)
153+
154+
// Start the server on port 3000
155+
log.Fatal(app.Listen(":3000"))
156+
}
157+
```
158+
159+
When you need to convert entire applications or re-use `net/http` middleware chains, rely on the [adaptor middleware](https://docs.gofiber.io/next/middleware/adaptor/). It converts handlers and middlewares in both directions and even lets you mount a Fiber app in a `net/http` server.
160+
161+
> **Note:** Adapted `net/http` handlers continue to operate with the standard-library semantics. They don't get access to `fiber.Ctx` features and incur the overhead of the compatibility layer, so native `fiber.Handler` callbacks still provide the best performance.
133162
134163
## 👀 Examples
135164

adapter.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package fiber
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"reflect"
7+
8+
"github.com/valyala/fasthttp"
9+
"github.com/valyala/fasthttp/fasthttpadaptor"
10+
)
11+
12+
// toFiberHandler converts a supported handler type to a Fiber handler.
13+
func toFiberHandler(handler any) (Handler, bool) {
14+
if handler == nil {
15+
return nil, false
16+
}
17+
18+
switch h := handler.(type) {
19+
case Handler:
20+
if h == nil {
21+
return nil, false
22+
}
23+
return h, true
24+
case http.HandlerFunc:
25+
if h == nil {
26+
return nil, false
27+
}
28+
return wrapHTTPHandler(h), true
29+
case http.Handler:
30+
if h == nil {
31+
return nil, false
32+
}
33+
hv := reflect.ValueOf(h)
34+
if hv.Kind() == reflect.Pointer && hv.IsNil() {
35+
return nil, false
36+
}
37+
return wrapHTTPHandler(h), true
38+
case func(http.ResponseWriter, *http.Request):
39+
if h == nil {
40+
return nil, false
41+
}
42+
return wrapHTTPHandler(http.HandlerFunc(h)), true
43+
case fasthttp.RequestHandler:
44+
if h == nil {
45+
return nil, false
46+
}
47+
return func(c Ctx) error {
48+
h(c.RequestCtx())
49+
return nil
50+
}, true
51+
default:
52+
return nil, false
53+
}
54+
}
55+
56+
// wrapHTTPHandler adapts a net/http handler to a Fiber handler.
57+
func wrapHTTPHandler(handler http.Handler) Handler {
58+
if handler == nil {
59+
return nil
60+
}
61+
62+
adapted := fasthttpadaptor.NewFastHTTPHandler(handler)
63+
64+
return func(c Ctx) error {
65+
adapted(c.RequestCtx())
66+
return nil
67+
}
68+
}
69+
70+
// collectHandlers converts a slice of handler arguments to Fiber handlers.
71+
// The context string is used to provide informative panic messages when an
72+
// unsupported handler type is encountered.
73+
func collectHandlers(context string, args ...any) []Handler {
74+
handlers := make([]Handler, 0, len(args))
75+
76+
for i, arg := range args {
77+
handler, ok := toFiberHandler(arg)
78+
79+
if !ok {
80+
panic(fmt.Sprintf("%s: invalid handler #%d (%T)\n", context, i, arg))
81+
}
82+
handlers = append(handlers, handler)
83+
}
84+
85+
return handlers
86+
}

adapter_test.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package fiber
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"github.com/valyala/fasthttp"
12+
)
13+
14+
func TestToFiberHandler_Nil(t *testing.T) {
15+
t.Parallel()
16+
17+
var handler Handler
18+
converted, ok := toFiberHandler(handler)
19+
require.False(t, ok)
20+
require.Nil(t, converted)
21+
}
22+
23+
func TestToFiberHandler_FiberHandler(t *testing.T) {
24+
t.Parallel()
25+
26+
fiberHandler := func(c Ctx) error { return c.SendStatus(http.StatusAccepted) }
27+
28+
converted, ok := toFiberHandler(fiberHandler)
29+
require.True(t, ok)
30+
require.NotNil(t, converted)
31+
require.Equal(t, reflect.ValueOf(fiberHandler).Pointer(), reflect.ValueOf(converted).Pointer())
32+
}
33+
34+
func TestCollectHandlers_HTTPHandler(t *testing.T) {
35+
t.Parallel()
36+
37+
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
38+
w.Header().Set("X-HTTP", "ok")
39+
w.WriteHeader(http.StatusTeapot)
40+
_, err := w.Write([]byte("http"))
41+
assert.NoError(t, err)
42+
})
43+
44+
handlers := collectHandlers("test", httpHandler)
45+
require.Len(t, handlers, 1)
46+
converted := handlers[0]
47+
require.NotNil(t, converted)
48+
49+
app := New()
50+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
51+
t.Cleanup(func() {
52+
app.ReleaseCtx(ctx)
53+
})
54+
55+
err := converted(ctx)
56+
require.NoError(t, err)
57+
require.Equal(t, http.StatusTeapot, ctx.Response().StatusCode())
58+
require.Equal(t, "ok", string(ctx.Response().Header.Peek("X-HTTP")))
59+
require.Equal(t, "http", string(ctx.Response().Body()))
60+
}
61+
62+
func TestToFiberHandler_HTTPHandler(t *testing.T) {
63+
t.Parallel()
64+
65+
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
66+
w.Header().Set("X-HTTP", "handler")
67+
_, err := w.Write([]byte("through"))
68+
assert.NoError(t, err)
69+
})
70+
71+
converted, ok := toFiberHandler(handler)
72+
require.True(t, ok)
73+
require.NotNil(t, converted)
74+
75+
app := New()
76+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
77+
t.Cleanup(func() {
78+
app.ReleaseCtx(ctx)
79+
})
80+
81+
err := converted(ctx)
82+
require.NoError(t, err)
83+
require.Equal(t, "handler", string(ctx.Response().Header.Peek("X-HTTP")))
84+
require.Equal(t, "through", string(ctx.Response().Body()))
85+
}
86+
87+
func TestToFiberHandler_HTTPHandlerFunc(t *testing.T) {
88+
t.Parallel()
89+
90+
httpFunc := func(w http.ResponseWriter, _ *http.Request) {
91+
w.WriteHeader(http.StatusNoContent)
92+
}
93+
94+
converted, ok := toFiberHandler(httpFunc)
95+
require.True(t, ok)
96+
require.NotNil(t, converted)
97+
98+
app := New()
99+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
100+
t.Cleanup(func() {
101+
app.ReleaseCtx(ctx)
102+
})
103+
104+
err := converted(ctx)
105+
require.NoError(t, err)
106+
require.Equal(t, http.StatusNoContent, ctx.Response().StatusCode())
107+
}
108+
109+
func TestWrapHTTPHandler_Nil(t *testing.T) {
110+
t.Parallel()
111+
112+
require.Nil(t, wrapHTTPHandler(nil))
113+
}
114+
115+
func TestCollectHandlers_InvalidType(t *testing.T) {
116+
t.Parallel()
117+
118+
require.PanicsWithValue(t, "context: invalid handler #0 (int)\n", func() {
119+
collectHandlers("context", 42)
120+
})
121+
}
122+
123+
func TestCollectHandlers_TypedNilHTTPHandlers(t *testing.T) {
124+
t.Parallel()
125+
126+
var handlerFunc http.HandlerFunc
127+
var handler http.Handler
128+
var raw func(http.ResponseWriter, *http.Request)
129+
130+
tests := []struct {
131+
handler any
132+
name string
133+
}{
134+
{
135+
name: "HandlerFunc",
136+
handler: handlerFunc,
137+
},
138+
{
139+
name: "Handler",
140+
handler: handler,
141+
},
142+
{
143+
name: "Function",
144+
handler: raw,
145+
},
146+
}
147+
148+
for _, tt := range tests {
149+
t.Run(tt.name, func(t *testing.T) {
150+
t.Parallel()
151+
152+
expected := fmt.Sprintf("context: invalid handler #0 (%T)\n", tt.handler)
153+
154+
require.PanicsWithValue(t, expected, func() {
155+
collectHandlers("context", tt.handler)
156+
})
157+
})
158+
}
159+
}
160+
161+
type dummyHandler struct{}
162+
163+
func (dummyHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}
164+
165+
func TestCollectHandlers_TypedNilPointerHTTPHandler(t *testing.T) {
166+
t.Parallel()
167+
168+
var handler http.Handler = (*dummyHandler)(nil)
169+
170+
require.PanicsWithValue(t, "context: invalid handler #0 (*fiber.dummyHandler)\n", func() {
171+
collectHandlers("context", handler)
172+
})
173+
}
174+
175+
func TestCollectHandlers_FasthttpHandler(t *testing.T) {
176+
t.Parallel()
177+
178+
before := func(c Ctx) error {
179+
c.Set("X-Before", "fiber")
180+
return nil
181+
}
182+
183+
fasthttpHandler := fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
184+
ctx.Response.Header.Set("X-FASTHTTP", "ok")
185+
ctx.SetBody([]byte("done"))
186+
})
187+
188+
handlers := collectHandlers("fasthttp", before, fasthttpHandler)
189+
require.Len(t, handlers, 2)
190+
191+
app := New()
192+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
193+
t.Cleanup(func() {
194+
app.ReleaseCtx(ctx)
195+
})
196+
197+
for _, handler := range handlers {
198+
require.NoError(t, handler(ctx))
199+
}
200+
201+
require.Equal(t, "fiber", string(ctx.Response().Header.Peek("X-Before")))
202+
require.Equal(t, "ok", string(ctx.Response().Header.Peek("X-FASTHTTP")))
203+
require.Equal(t, "done", string(ctx.Response().Body()))
204+
}
205+
206+
func TestCollectHandlers_MixedHandlers(t *testing.T) {
207+
t.Parallel()
208+
209+
before := func(c Ctx) error {
210+
c.Set("X-Before", "fiber")
211+
return nil
212+
}
213+
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
214+
_, err := w.Write([]byte("done"))
215+
assert.NoError(t, err)
216+
})
217+
218+
handlers := collectHandlers("test", before, httpHandler)
219+
require.Len(t, handlers, 2)
220+
require.Equal(t, reflect.ValueOf(before).Pointer(), reflect.ValueOf(handlers[0]).Pointer())
221+
require.NotNil(t, handlers[1])
222+
223+
app := New()
224+
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
225+
t.Cleanup(func() {
226+
app.ReleaseCtx(ctx)
227+
})
228+
229+
err := handlers[0](ctx)
230+
require.NoError(t, err)
231+
232+
err = handlers[1](ctx)
233+
require.NoError(t, err)
234+
require.Equal(t, "done", string(ctx.Response().Body()))
235+
require.Equal(t, "fiber", string(ctx.Response().Header.Peek("X-Before")))
236+
}
237+
238+
func TestCollectHandlers_Nil(t *testing.T) {
239+
t.Parallel()
240+
241+
require.PanicsWithValue(t, "nil: invalid handler #0 (<nil>)\n", func() {
242+
collectHandlers("nil", nil)
243+
})
244+
}

0 commit comments

Comments
 (0)