Skip to content
Merged
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
33 changes: 31 additions & 2 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,40 @@ We **listen** to our users in [issues](https://github.com/gofiber/fiber/issues),
## ⚠️ Limitations

- 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.
- 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`.
- 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.

### net/http compatibility

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.
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:

```go
package main

import (
"log"
"net/http"

"github.com/gofiber/fiber/v3"
)

func main() {
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte("served by net/http")); err != nil {
panic(err)
}
})

app := fiber.New()
app.Get("/", httpHandler)

// Start the server on port 3000
log.Fatal(app.Listen(":3000"))
}
```

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.

> **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.

## 👀 Examples

Expand Down
86 changes: 86 additions & 0 deletions adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package fiber

import (
"fmt"
"net/http"
"reflect"

"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
)

// toFiberHandler converts a supported handler type to a Fiber handler.
func toFiberHandler(handler any) (Handler, bool) {
if handler == nil {
return nil, false
}

switch h := handler.(type) {
case Handler:
if h == nil {
return nil, false
}
return h, true
case http.HandlerFunc:
if h == nil {
return nil, false
}
return wrapHTTPHandler(h), true
case http.Handler:
if h == nil {
return nil, false
}
hv := reflect.ValueOf(h)
if hv.Kind() == reflect.Pointer && hv.IsNil() {
return nil, false
}
return wrapHTTPHandler(h), true
case func(http.ResponseWriter, *http.Request):
if h == nil {
return nil, false
}
return wrapHTTPHandler(http.HandlerFunc(h)), true
case fasthttp.RequestHandler:
if h == nil {
return nil, false
}
return func(c Ctx) error {
h(c.RequestCtx())
return nil
}, true
default:
return nil, false
}
}

// wrapHTTPHandler adapts a net/http handler to a Fiber handler.
func wrapHTTPHandler(handler http.Handler) Handler {
if handler == nil {
return nil
}

adapted := fasthttpadaptor.NewFastHTTPHandler(handler)

return func(c Ctx) error {
adapted(c.RequestCtx())
return nil
}
}

// collectHandlers converts a slice of handler arguments to Fiber handlers.
// The context string is used to provide informative panic messages when an
// unsupported handler type is encountered.
func collectHandlers(context string, args ...any) []Handler {
handlers := make([]Handler, 0, len(args))

for i, arg := range args {
handler, ok := toFiberHandler(arg)

if !ok {
panic(fmt.Sprintf("%s: invalid handler #%d (%T)\n", context, i, arg))
}
handlers = append(handlers, handler)
}

return handlers
}
244 changes: 244 additions & 0 deletions adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package fiber

import (
"fmt"
"net/http"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)

func TestToFiberHandler_Nil(t *testing.T) {
t.Parallel()

var handler Handler
converted, ok := toFiberHandler(handler)
require.False(t, ok)
require.Nil(t, converted)
}

func TestToFiberHandler_FiberHandler(t *testing.T) {
t.Parallel()

fiberHandler := func(c Ctx) error { return c.SendStatus(http.StatusAccepted) }

converted, ok := toFiberHandler(fiberHandler)
require.True(t, ok)
require.NotNil(t, converted)
require.Equal(t, reflect.ValueOf(fiberHandler).Pointer(), reflect.ValueOf(converted).Pointer())
}

func TestCollectHandlers_HTTPHandler(t *testing.T) {
t.Parallel()

httpHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("X-HTTP", "ok")
w.WriteHeader(http.StatusTeapot)
_, err := w.Write([]byte("http"))
assert.NoError(t, err)
})

handlers := collectHandlers("test", httpHandler)
require.Len(t, handlers, 1)
converted := handlers[0]
require.NotNil(t, converted)

app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(ctx)
})

err := converted(ctx)
require.NoError(t, err)
require.Equal(t, http.StatusTeapot, ctx.Response().StatusCode())
require.Equal(t, "ok", string(ctx.Response().Header.Peek("X-HTTP")))
require.Equal(t, "http", string(ctx.Response().Body()))
}

func TestToFiberHandler_HTTPHandler(t *testing.T) {
t.Parallel()

var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("X-HTTP", "handler")
_, err := w.Write([]byte("through"))
assert.NoError(t, err)
})

converted, ok := toFiberHandler(handler)
require.True(t, ok)
require.NotNil(t, converted)

app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(ctx)
})

err := converted(ctx)
require.NoError(t, err)
require.Equal(t, "handler", string(ctx.Response().Header.Peek("X-HTTP")))
require.Equal(t, "through", string(ctx.Response().Body()))
}

func TestToFiberHandler_HTTPHandlerFunc(t *testing.T) {
t.Parallel()

httpFunc := func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}

converted, ok := toFiberHandler(httpFunc)
require.True(t, ok)
require.NotNil(t, converted)

app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(ctx)
})

err := converted(ctx)
require.NoError(t, err)
require.Equal(t, http.StatusNoContent, ctx.Response().StatusCode())
}

func TestWrapHTTPHandler_Nil(t *testing.T) {
t.Parallel()

require.Nil(t, wrapHTTPHandler(nil))
}

func TestCollectHandlers_InvalidType(t *testing.T) {
t.Parallel()

require.PanicsWithValue(t, "context: invalid handler #0 (int)\n", func() {
collectHandlers("context", 42)
})
}

func TestCollectHandlers_TypedNilHTTPHandlers(t *testing.T) {
t.Parallel()

var handlerFunc http.HandlerFunc
var handler http.Handler
var raw func(http.ResponseWriter, *http.Request)

tests := []struct {
handler any
name string
}{
{
name: "HandlerFunc",
handler: handlerFunc,
},
{
name: "Handler",
handler: handler,
},
{
name: "Function",
handler: raw,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

expected := fmt.Sprintf("context: invalid handler #0 (%T)\n", tt.handler)

require.PanicsWithValue(t, expected, func() {
collectHandlers("context", tt.handler)
})
})
}
}

type dummyHandler struct{}

func (dummyHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}

func TestCollectHandlers_TypedNilPointerHTTPHandler(t *testing.T) {
t.Parallel()

var handler http.Handler = (*dummyHandler)(nil)

require.PanicsWithValue(t, "context: invalid handler #0 (*fiber.dummyHandler)\n", func() {
collectHandlers("context", handler)
})
}

func TestCollectHandlers_FasthttpHandler(t *testing.T) {
t.Parallel()

before := func(c Ctx) error {
c.Set("X-Before", "fiber")
return nil
}

fasthttpHandler := fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
ctx.Response.Header.Set("X-FASTHTTP", "ok")
ctx.SetBody([]byte("done"))
})

handlers := collectHandlers("fasthttp", before, fasthttpHandler)
require.Len(t, handlers, 2)

app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(ctx)
})

for _, handler := range handlers {
require.NoError(t, handler(ctx))
}

require.Equal(t, "fiber", string(ctx.Response().Header.Peek("X-Before")))
require.Equal(t, "ok", string(ctx.Response().Header.Peek("X-FASTHTTP")))
require.Equal(t, "done", string(ctx.Response().Body()))
}

func TestCollectHandlers_MixedHandlers(t *testing.T) {
t.Parallel()

before := func(c Ctx) error {
c.Set("X-Before", "fiber")
return nil
}
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, err := w.Write([]byte("done"))
assert.NoError(t, err)
})

handlers := collectHandlers("test", before, httpHandler)
require.Len(t, handlers, 2)
require.Equal(t, reflect.ValueOf(before).Pointer(), reflect.ValueOf(handlers[0]).Pointer())
require.NotNil(t, handlers[1])

app := New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
t.Cleanup(func() {
app.ReleaseCtx(ctx)
})

err := handlers[0](ctx)
require.NoError(t, err)

err = handlers[1](ctx)
require.NoError(t, err)
require.Equal(t, "done", string(ctx.Response().Body()))
require.Equal(t, "fiber", string(ctx.Response().Header.Peek("X-Before")))
}

func TestCollectHandlers_Nil(t *testing.T) {
t.Parallel()

require.PanicsWithValue(t, "nil: invalid handler #0 (<nil>)\n", func() {
collectHandlers("nil", nil)
})
}
Loading
Loading