Adaptor
The adaptor package converts between Fiber and net/http, letting you reuse handlers, middleware, and requests across both frameworks.
Fiber can register plain net/http handlers directlyβjust pass an http.Handler,
http.HandlerFunc, or func(http.ResponseWriter, *http.Request) to any router
method and it will be adapted automatically, with the same routed-path handling as
HTTPHandler (see notes and limitations). The adaptor helpers remain valuable
when you need to convert middleware, swap handler directions, or transform
requests explicitly.
Even when you register them directly, adapted net/http handlers still run with standard
library semantics. They don't have access to fiber.Ctx, and the compatibility layer comes
with additional overhead compared to native Fiber handlers. Use them for interop and legacy
scenarios, but prefer Fiber handlers when performance or Fiber-specific APIs matter.
Featuresβ
- Convert
net/httphandlers and middleware to Fiber handlers - Convert Fiber handlers to
net/httphandlers - Convert a Fiber context (
fiber.Ctx) into anhttp.Request - Copy values stored in a
context.Contextonto afasthttp.RequestCtx
When Fiber is executed from a net/http server through FiberHandler, FiberHandlerFunc,
or FiberApp, the adaptor enforces the app's configured BodyLimit. The app's BodyLimit defaults to 4 MiB if a non-positive value is provided during configuration. FiberHandler and FiberHandlerFunc run the handler on a fresh app with the default configuration, so they always apply the 4 MiB default; pass your own app to FiberApp to use a different BodyLimit. Requests exceeding the active limit receive 413 Request Entity Too Large.
HTTPHandler, HTTPHandlerFunc, HTTPHandlerWithContext, HTTPMiddleware and ConvertRequest buffer the request body to build the http.Request. With StreamRequestBody they read it within the app's BodyLimit and answer a larger one with 413 Request Entity Too Large, and a body that fails to read, such as a malformed chunk, with 400 Bad Request (ConvertRequest returns fiber.ErrRequestEntityTooLarge or fiber.ErrBadRequest). A read error that already carries a *fiber.Error keeps its status.
API Referenceβ
| Name | Signature | Description |
|---|---|---|
HTTPHandler | HTTPHandler(h http.Handler) fiber.Handler | Converts http.Handler to fiber.Handler |
HTTPHandlerWithContext | HTTPHandlerWithContext(h http.Handler) fiber.Handler | Converts http.Handler to fiber.Handler, propagating Fiber's local context |
HTTPHandlerFunc | HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler | Converts http.HandlerFunc to fiber.Handler |
HTTPMiddleware | HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler | Converts http.Handler middleware to fiber.Handler middleware |
FiberHandler | FiberHandler(h fiber.Handler) http.Handler | Converts fiber.Handler to http.Handler |
FiberHandlerFunc | FiberHandlerFunc(h fiber.Handler) http.HandlerFunc | Converts fiber.Handler to http.HandlerFunc |
FiberApp | FiberApp(app *fiber.App) http.HandlerFunc | Converts an entire Fiber app to a http.HandlerFunc |
ConvertRequest | ConvertRequest(c fiber.Ctx, forServer bool) (*http.Request, error) | Converts fiber.Ctx into a http.Request |
LocalContextFromHTTPRequest | LocalContextFromHTTPRequest(r *http.Request) (context.Context, bool) | Extracts the propagated context.Context from an adapted http.Request |
CopyContextToFiberContext | CopyContextToFiberContext(src any, requestContext *fasthttp.RequestCtx) | Copies context.Context to fasthttp.RequestCtx |
Examplesβ
1. Using net/http handlers in Fiber (HTTPHandler, HTTPHandlerFunc)β
Run standard net/http handlers inside Fiber. Fiber can auto-adapt them, or you can
explicitly convert them when you want to cache or share the converted handler.
package main
import (
"fmt"
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
app := fiber.New()
// Fiber adapts net/http handlers for you during registration.
app.Get("/", http.HandlerFunc(helloHandler))
// You can also convert and reuse the handler manually.
cached := adaptor.HTTPHandler(http.HandlerFunc(helloHandler))
app.Get("/cached", cached)
// When you already have an http.HandlerFunc, convert it directly.
app.Get("/func", adaptor.HTTPHandlerFunc(helloHandler))
app.Listen(":3000")
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello from net/http!")
}
2. Using net/http middleware with Fiber (HTTPMiddleware)β
Middleware written for net/http can run inside Fiber:
package main
import (
"log"
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
app := fiber.New()
// Apply an http middleware in Fiber
app.Use(adaptor.HTTPMiddleware(loggingMiddleware))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello Fiber!")
})
app.Listen(":3000")
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Request received")
next.ServeHTTP(w, r)
})
}
3. Using Fiber handlers in net/http (FiberHandler)β
You can use Fiber handlers from net/http:
package main
import (
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
// Convert a Fiber handler to an http.Handler
http.Handle("/", adaptor.FiberHandler(helloFiber))
// Convert a Fiber handler to an http.HandlerFunc
http.HandleFunc("/func", adaptor.FiberHandlerFunc(helloFiber))
http.ListenAndServe(":3000", nil)
}
func helloFiber(c fiber.Ctx) error {
return c.SendString("Hello from Fiber!")
}
4. Converting Fiber handlers to http.HandlerFunc (FiberHandlerFunc)β
When you specifically need an http.HandlerFunc, wrap the Fiber handler directly:
package main
import (
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
http.HandleFunc("/func-only", adaptor.FiberHandlerFunc(helloFiber))
http.ListenAndServe(":3000", nil)
}
func helloFiber(c fiber.Ctx) error {
return c.SendString("Hello from Fiber!")
}
5. Running a full Fiber app inside net/http (FiberApp)β
You can wrap a full Fiber app inside net/http:
package main
import (
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello from Fiber!")
})
// Run Fiber inside an http server
http.ListenAndServe(":3000", adaptor.FiberApp(app))
}
The net/http server and the Fiber app behind it each parse the request's headers, and they do not read a repeated header alike. Header.Get returns the first field line even when it is empty, while c.Get returns the first line that holds a value. A request carrying X-Internal: on one line and X-Internal: true on the next therefore reads as empty in a net/http guard and as true in the Fiber handler behind it. A guard that must keep clients from setting a header should strip it with r.Header.Del(name) or test len(r.Header.Values(name)) > 0, never compare r.Header.Get(name) with the empty string. Fiber's basicauth and csrf middleware refuse a request that repeats the single-value fields they read, and so does keyauth with its default Authorization extractor, so a credential split across two Authorization lines is rejected rather than authenticated; a custom extractors.FromHeader extractor combines repeated lines into one value, which the validator then judges.
6. Converting fiber.Ctx to *http.Request (ConvertRequest)β
Create an *http.Request from a fiber.Ctx. The forServer parameter decides whether
the request carries a RequestURI; RemoteAddr and TLS are filled in either way:
- Use
forServer = truewhen the converted request will be passed into anet/httphandler (setsRequestURI, which a server-side request carries) - Use
forServer = falsewhen creating a request for client-side use (e.g., making an outbound HTTP request withhttp.Client, which refuses a request that hasRequestURIset)
package main
import (
"net/http"
"net/http/httptest"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
func main() {
app := fiber.New()
app.Get("/request", handleRequest)
app.Listen(":3000")
}
func handleRequest(c fiber.Ctx) error {
// Use forServer = true when passing to a net/http handler
httpReq, err := adaptor.ConvertRequest(c, true)
if err != nil {
return err
}
// Pass the request to a net/http handler.
recorder := httptest.NewRecorder()
http.DefaultServeMux.ServeHTTP(recorder, httpReq)
return c.SendString("Converted Request URL: " + httpReq.URL.String())
}
7. Passing Fiber user context into net/httpβ
This example shows a realistic flow: a Fiber middleware sets a request-scoped context.Context (with a request_id) on the Fiber context, then an adapted net/http handler retrieves it via LocalContextFromHTTPRequest.
package main
import (
"context"
"fmt"
"net/http"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
type ctxKey string
const requestIDKey ctxKey = "request_id"
func main() {
app := fiber.New()
// Create a request-scoped context in Fiber (e.g., request id, auth claims, trace span).
app.Use(func(c fiber.Ctx) error {
reqID := c.Get("X-Request-ID")
ctx := context.WithValue(context.Background(), requestIDKey, reqID)
// Fiber stores request-scoped context as "user context".
c.SetContext(ctx)
return c.Next()
})
// 2) Run a standard net/http handler that includes Fiber's user context propagated.
app.Get("/hello", adaptor.HTTPHandlerWithContext(http.HandlerFunc(handleRequest)))
app.Listen(":3000")
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
ctx, ok := adaptor.LocalContextFromHTTPRequest(r)
if !ok || ctx == nil {
http.Error(w, "missing propagated context", http.StatusInternalServerError)
return
}
reqID, _ := ctx.Value(requestIDKey).(string)
fmt.Fprintf(w, "Hello from net/http (request_id=%s)\n", reqID)
}
8. Copying context values onto fasthttp.RequestCtx (CopyContextToFiberContext)β
CopyContextToFiberContext copies values stored in a context.Context onto a
fasthttp.RequestCtx. The function is marked deprecated in code because it uses
reflection and unsafe operationsβprefer explicit parameter passing when possible.
When you do need it, call it immediately after you add values to the net/http
context so Fiber can read them via c.Locals(). Values are copied from the
chain of derived contexts (WithValue, WithCancel, WithTimeout,
WithDeadline, WithoutCancel), the most recently derived context winning for
a key set twice, as Value reports it. The walk follows the chain to its end
and stops only where it would repeat itself, so a deep middleware stack keeps
its outermost values and a context pointing back at itself still terminates:
package main
import (
"context"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
type contextKey string
func main() {
app := fiber.New()
app.Use(func(c fiber.Ctx) error {
// Convert the Fiber context to an http.Request so we can attach context values.
httpReq, err := adaptor.ConvertRequest(c, true)
if err != nil {
return err
}
// Add context data and push it back to the Fiber context.
enriched := httpReq.WithContext(context.WithValue(httpReq.Context(), contextKey("requestID"), "req-123"))
adaptor.CopyContextToFiberContext(enriched.Context(), c.RequestCtx())
return c.Next()
})
app.Get("/", func(c fiber.Ctx) error {
if id, ok := c.Locals(contextKey("requestID")).(string); ok {
return c.SendString("Request ID: " + id)
}
return c.SendStatus(fiber.StatusNotFound)
})
app.Listen(":3000")
}
Notes and limitationsβ
- An adapted
net/httphandler, middleware orConvertRequestrequest is built from the path the router matched,c.Path(), followed by the query as the client sent it, not from the request line as it arrived. The two differ for an escaped or non-canonical request, andnet/httpreads the raw line its own way:/public/..%2Fadmin/xdecodes into aURL.Paththathttp.FileServercleans to/admin/x, although no middleware mounted on/adminhas run for it. Routed,/a/../admin/xreaches the handler as/admin/x(after the/adminmiddleware ran),%41asA, a stray%as%25, a byte a path may not carry raw, such as|or a byte of UTF-8, as its escape, and withUnescapePaththe decoded path is escaped again segment by segment. The query keeps its bytes unless a Fiber handler changed the arguments throughQueryArgs(). - The adapted request keeps the host Fiber saw. For a request in the absolute form, such as
GET http://public.example/xwithHost: admin.internal, that is the host the request line names (RFC 9112 Section 3.2.2):r.Hostispublic.example, as arec.Hostname()and the hostapp.Domainmatches. HTTPHandler,HTTPHandlerFuncandHTTPHandlerWithContextanswer404 Not Foundfor a routed path with an empty segment (//admin/x) or an escaped slash (/public/..%2Fadmin/x):net/httpdecodes%2Finto a separator inURL.Pathand its handlers clean//and..away, so the handler would serve a path the router never matched. They answer404as well for a backslash, raw or escaped as%5C, and for a.or..segment carrying parameters, such as..;, or a segment of parameters alone before another, such as/;x/admin, which a handler that passes the request on, such ashttputil.ReverseProxy, would hand to an upstream that reads them as a separator (WHATWG URL parsers, IIS), as a dot segment or as an empty one (Tomcat, Jetty). A path parameter on a named segment, such as/a;b/c, is handed on, sincenet/httpreads it as part of the name. A middleware mounted on/admindoes not run for/admin;x/secret, so put an upstream that strips parameters, such as Tomcat or Jetty, behind the proxy middleware, which refuses it, rather than an adaptedhttputil.ReverseProxy.HTTPMiddlewareandConvertRequesthand all of these on, with an escaped slash kept inURL.RawPath; a Fiber route behind the middleware still receives its%2F. To serve names that hold one, such as a GitLab-stylegroup%2Fprojectid, wrap the handler withHTTPMiddlewareas a middleware that does not callnext, and read the name fromr.URL.EscapedPath().HTTPMiddlewareanswers400 Bad Request, andConvertRequestreturnsfiber.ErrBadRequest, for a routed path fasthttp would read as an authority rather than a path: one that begins with//and holds://, which..resolution builds out of//u:[email protected]/a:/x/..//b, or any path beginning with//on a request without aHostheader. Set as the request line, it would become the host of both thenet/httprequest and the Fiber request behind the middleware.- A routed path holding an escape of an unreserved character, such as
%2eor%41, is answered with404 Not FoundbyHTTPHandler,HTTPHandlerFuncandHTTPHandlerWithContextand with400 Bad RequestbyHTTPMiddlewareandConvertRequest. The router decodes every such escape when it normalizes a path, so one still inc.Path()can only have been forged by a stray%, andURL.Pathwould read a forged%2e%2eas the..segment anhttp.FileServercleans away. HTTPHandler,HTTPHandlerFuncandHTTPHandlerWithContextput the original request line back once a buffered handler has returned, or panicked, soc.OriginalURL()in a middleware that runs afterwards is unchanged. A handler that flushed or hijacked is still running with a request whose URL aliases the request line, so the line is then left as the handler read it;HTTPMiddlewareandConvertRequestleave it as well.- A request that
net/httpserved over TLS is seen as TLS by Fiber:c.Scheme()ishttps,c.Secure()is true andc.RequestCtx().TLSConnectionState()carries the state fromr.TLS. - A request the timeout middleware gave up on behind
FiberHandler,FiberHandlerFuncorFiberAppis answered with its timeout response, as on Fiber's own server. Itsfasthttp.RequestCtxis left to the handler that may still run on it and never goes back to the pool, so no later request shares it and the handler'sc.Context()keeps reporting the timeout. FiberHandler,FiberHandlerFuncandFiberAppclose the body stream of a response to aHEADrequest unread, as Fiber's own server does:net/httpdiscards aHEADbody, so a stream writer or an SSEHandlerwould otherwise run with every write succeeding.- For a request that reaches Fiber through
FiberHandler,FiberHandlerFuncorFiberApp,c.StartTime()marks the moment the adaptor took the request over, not the moment thenet/httpserver began reading it, soc.Elapsed()leaves out the time spent before the handler was called. - Both parsers read the headers:
Header.Getreturns the first field line even when it is empty,c.Getthe first line that holds a value. Anet/httpguard should strip a header or check every value rather than compareHeader.Getwith the empty string; see the caution in theFiberAppsection above. HTTPMiddlewareroutes the request the wrapped middleware hands tonext, including a rewrittenr.URLsuch as the onehttp.StripPrefixproduces. The middleware must callnextbefore it flushes or hijacks the response; after that the response has left Fiber's hands and the call is ignored.HTTPMiddlewarecopiesr.Method,r.Hostandr.Header, as the wrapped middleware hands them tonext, back onto the Fiber request, so a header it replaces or deletes (r.Header.Set,r.Header.Del) is replaced or gone for the handlers behind it,c.IPs()included. A changedr.Methodreaches only the raw request (c.Request().Header.Method()):c.Method()and the route that is running keep the method the request was routed with, so a rewrite does not move it to another method's handlers.Content-LengthandTransfer-Encodingare not copied back, so the body framing stays as received. Fiber drops the trailer section of a chunked request as the request arrives or, withStreamRequestBody, once its body has been read, sor.Traileris empty and a trailer cannot stand in for a header the middleware removed or replaced. AConnectionfield written as several entries is joined with commas, and aclosetoken anywhere in it closes the connection once the response is sent.FiberHandler,FiberHandlerFuncandFiberApprouter.RequestURI, the request line asnet/httpreceived it, so a raw/ΓΌberor/a|bis routed and its params are read as a Fiber server would read them. Whenr.URLno longer says what that line says, or there is no such line, they route the request linenet/httpwould write forr.URLinstead: the path anhttp.StripPrefixin front of them rewrote, the URL of a request built in code, which has noRequestURIat all, and, for aCONNECTrequest in the authority form, the authority rather than the/thatr.URL.RequestURI()reads for it:r.URL.Opaquewhen set, elser.Host, elser.URL.Host, the order in whichnet/httpitself writes such a request.FiberHandler,FiberHandlerFuncandFiberApppass repeated lines of a header such asX-Forwarded-Foron as repeated lines, whichc.IP(),c.Scheme()and the proxy middleware read in the ordernet/httpreceived them even after a header line was removed, andc.Protocol()reports the version the request arrived with (HTTP/1.0,HTTP/1.1,HTTP/2orHTTP/3). The client address comes fromr.RemoteAddr: a host without a port gets port80, and behind a Unix socket listener it is the listener's own address, which holds no client IP. An address that is empty or cannot be resolved does not fail the request;c.IP()is then an empty string.c.SendEarlyHintsdoes not panic, but its103interim response is discarded because there is no client connection to write it to; theLinkheaders still reach the final response.
Summaryβ
The adaptor package lets Fiber and net/http interoperate so you can:
- Convert handlers and middleware in both directions
- Run Fiber apps inside
net/http - Convert
fiber.Ctxtohttp.Request - Propagate Fiber's user context into adapted
net/httphandlers
This makes it straightforward to integrate Fiber with existing Go projects or migrate between frameworks.