mirror of
https://github.com/zap-proto/fiber.git
synced 2026-10-30 15:06:23 +00:00
Fork of gofiber/fiber v3 based on the v3.2.0 tag. Rewrites all self-imports github.com/gofiber/fiber/v3 -> github.com/zap-proto/fiber/v3. Upstream gofiber/schema and gofiber/utils/v2 dependencies are left untouched. Co-authored-by: Hanzo Dev <[email protected]>
339 lines
9.1 KiB
Go
339 lines
9.1 KiB
Go
package adaptor
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"net/http"
|
||
"reflect"
|
||
"sync"
|
||
"unsafe"
|
||
|
||
"github.com/zap-proto/fiber/v3"
|
||
"github.com/gofiber/utils/v2"
|
||
"github.com/valyala/fasthttp"
|
||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||
)
|
||
|
||
// disableLogger implements the fasthttp Logger interface and discards log output.
|
||
type disableLogger struct{}
|
||
|
||
// Printf implements the fasthttp Logger interface and discards log output.
|
||
func (*disableLogger) Printf(string, ...any) {
|
||
}
|
||
|
||
var ctxPool = sync.Pool{
|
||
New: func() any {
|
||
return new(fasthttp.RequestCtx)
|
||
},
|
||
}
|
||
|
||
// LocalContextKey is the key used to store the user's context.Context in the fasthttp request context.
|
||
// Adapted http.Handler functions can retrieve this context using r.Context().Value(adaptor.LocalContextKey)
|
||
var localContextKey = &struct{}{}
|
||
|
||
const bufferSize = 32 * 1024
|
||
|
||
var bufferPool = sync.Pool{
|
||
New: func() any {
|
||
return new([bufferSize]byte)
|
||
},
|
||
}
|
||
|
||
var (
|
||
ErrRemoteAddrEmpty = errors.New("remote address cannot be empty")
|
||
ErrRemoteAddrTooLong = errors.New("remote address too long")
|
||
)
|
||
|
||
// HTTPHandlerFunc wraps net/http handler func to fiber handler
|
||
func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler {
|
||
return HTTPHandler(h)
|
||
}
|
||
|
||
// HTTPHandler wraps net/http handler to fiber handler
|
||
func HTTPHandler(h http.Handler) fiber.Handler {
|
||
handler := fasthttpadaptor.NewFastHTTPHandler(h)
|
||
return func(c fiber.Ctx) error {
|
||
handler(c.RequestCtx())
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// HTTPHandlerWithContext is like HTTPHandler, but additionally stores Fiber’s user context in the request context
|
||
func HTTPHandlerWithContext(h http.Handler) fiber.Handler {
|
||
handler := fasthttpadaptor.NewFastHTTPHandler(h)
|
||
return func(c fiber.Ctx) error {
|
||
// Store the Fiber user context (c.Context()) in the fasthttp request context
|
||
// so adapted net/http handlers can retrieve it via adaptor.LocalContextFromHTTPRequest(r)
|
||
c.RequestCtx().SetUserValue(localContextKey, c.Context())
|
||
|
||
handler(c.RequestCtx())
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// LocalContextFromHTTPRequest extracts the Fiber user context previously stored into r.Context() by the adaptor.
|
||
func LocalContextFromHTTPRequest(r *http.Request) (context.Context, bool) {
|
||
if r == nil {
|
||
return nil, false
|
||
}
|
||
|
||
ctx, err := r.Context().Value(localContextKey).(context.Context)
|
||
return ctx, err
|
||
}
|
||
|
||
// ConvertRequest converts a fiber.Ctx to a http.Request.
|
||
// forServer should be set to true when the http.Request is going to be passed to a http.Handler.
|
||
func ConvertRequest(c fiber.Ctx, forServer bool) (*http.Request, error) {
|
||
var req http.Request
|
||
if err := fasthttpadaptor.ConvertRequest(c.RequestCtx(), &req, forServer); err != nil {
|
||
return nil, err //nolint:wrapcheck // This must not be wrapped
|
||
}
|
||
return &req, nil
|
||
}
|
||
|
||
// CopyContextToFiberContext copies the values of context.Context to a fasthttp.RequestCtx.
|
||
// This function safely handles struct fields, using unsafe operations only when necessary for unexported fields.
|
||
//
|
||
// Deprecated: This function uses reflection and unsafe pointers; consider using explicit context passing.
|
||
func CopyContextToFiberContext(src any, requestContext *fasthttp.RequestCtx) {
|
||
if requestContext == nil {
|
||
return
|
||
}
|
||
|
||
v := reflect.ValueOf(src)
|
||
if !v.IsValid() {
|
||
return
|
||
}
|
||
// Deref pointer chains
|
||
for v.Kind() == reflect.Ptr {
|
||
if v.IsNil() {
|
||
return
|
||
}
|
||
v = v.Elem()
|
||
}
|
||
t := v.Type()
|
||
if t.Kind() != reflect.Struct {
|
||
return
|
||
}
|
||
// Ensure addressable for safe unsafe-access of unexported fields
|
||
if !v.CanAddr() {
|
||
tmp := reflect.New(t)
|
||
tmp.Elem().Set(v)
|
||
v = tmp.Elem()
|
||
}
|
||
contextValues := v
|
||
contextKeys := t
|
||
|
||
var lastKey any
|
||
for i := 0; i < contextValues.NumField(); i++ {
|
||
reflectValue := contextValues.Field(i)
|
||
reflectField := contextKeys.Field(i)
|
||
|
||
if reflectField.Name == "noCopy" {
|
||
break
|
||
}
|
||
|
||
// Avoid unsafe access for unexported fields; use safe reflection where possible
|
||
if !reflectValue.CanInterface() {
|
||
/* #nosec */
|
||
reflectValue = reflect.NewAt(reflectValue.Type(), unsafe.Pointer(reflectValue.UnsafeAddr())).Elem()
|
||
}
|
||
|
||
switch reflectField.Name {
|
||
case "Context":
|
||
CopyContextToFiberContext(reflectValue.Interface(), requestContext)
|
||
case "key":
|
||
lastKey = reflectValue.Interface()
|
||
case "val":
|
||
if lastKey != nil {
|
||
requestContext.SetUserValue(lastKey, reflectValue.Interface())
|
||
lastKey = nil
|
||
}
|
||
default:
|
||
continue
|
||
}
|
||
}
|
||
}
|
||
|
||
// HTTPMiddleware wraps net/http middleware to fiber middleware
|
||
func HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler {
|
||
return func(c fiber.Ctx) error {
|
||
var next bool
|
||
nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
||
next = true
|
||
c.Request().Header.SetMethod(r.Method)
|
||
c.Request().SetRequestURI(r.RequestURI)
|
||
c.Request().SetHost(r.Host)
|
||
c.Request().Header.SetHost(r.Host)
|
||
|
||
// Remove all cookies before setting, see https://github.com/valyala/fasthttp/pull/1864
|
||
c.Request().Header.DelAllCookies()
|
||
for key, val := range r.Header {
|
||
for _, v := range val {
|
||
c.Request().Header.Set(key, v)
|
||
}
|
||
}
|
||
CopyContextToFiberContext(r.Context(), c.RequestCtx())
|
||
})
|
||
|
||
if err := HTTPHandler(mw(nextHandler))(c); err != nil {
|
||
return err
|
||
}
|
||
|
||
if next {
|
||
return c.Next()
|
||
}
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// FiberHandler wraps fiber handler to net/http handler
|
||
func FiberHandler(h fiber.Handler) http.Handler {
|
||
return FiberHandlerFunc(h)
|
||
}
|
||
|
||
// FiberHandlerFunc wraps fiber handler to net/http handler func
|
||
func FiberHandlerFunc(h fiber.Handler) http.HandlerFunc {
|
||
return handlerFunc(fiber.New(), h)
|
||
}
|
||
|
||
// FiberApp wraps fiber app to net/http handler func
|
||
func FiberApp(app *fiber.App) http.HandlerFunc {
|
||
return handlerFunc(app)
|
||
}
|
||
|
||
func isUnixNetwork(network string) bool {
|
||
return network == "unix" || network == "unixgram" || network == "unixpacket"
|
||
}
|
||
|
||
func resolveRemoteAddr(remoteAddr string, localAddr any) (net.Addr, error) {
|
||
if addr, ok := localAddr.(net.Addr); ok && isUnixNetwork(addr.Network()) {
|
||
return addr, nil
|
||
}
|
||
|
||
// Validate input to prevent malformed addresses
|
||
if remoteAddr == "" {
|
||
return nil, ErrRemoteAddrEmpty
|
||
}
|
||
|
||
resolved, err := net.ResolveTCPAddr("tcp", remoteAddr)
|
||
if err == nil {
|
||
return resolved, nil
|
||
}
|
||
|
||
var addrErr *net.AddrError
|
||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
||
if len(remoteAddr) > 253 { // Max hostname length
|
||
return nil, ErrRemoteAddrTooLong
|
||
}
|
||
remoteAddr = net.JoinHostPort(remoteAddr, "80")
|
||
resolved, err2 := net.ResolveTCPAddr("tcp", remoteAddr)
|
||
if err2 != nil {
|
||
return nil, fmt.Errorf("failed to resolve TCP address after adding port: %w", err2)
|
||
}
|
||
return resolved, nil
|
||
}
|
||
return nil, fmt.Errorf("failed to resolve TCP address: %w", err)
|
||
}
|
||
|
||
func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
req := fasthttp.AcquireRequest()
|
||
defer fasthttp.ReleaseRequest(req)
|
||
|
||
// Convert net/http -> fasthttp request with size limit
|
||
maxBodySize := int64(app.Config().BodyLimit)
|
||
if r.Body != nil {
|
||
if r.ContentLength > maxBodySize {
|
||
http.Error(w, utils.StatusMessage(fiber.StatusRequestEntityTooLarge), fiber.StatusRequestEntityTooLarge)
|
||
return
|
||
}
|
||
limitedReader := io.LimitReader(r.Body, maxBodySize)
|
||
n, err := io.Copy(req.BodyWriter(), limitedReader)
|
||
req.Header.SetContentLength(int(n))
|
||
|
||
if err != nil {
|
||
http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
req.Header.SetMethod(r.Method)
|
||
req.SetRequestURI(r.RequestURI)
|
||
req.SetHost(r.Host)
|
||
req.Header.SetHost(r.Host)
|
||
|
||
for key, val := range r.Header {
|
||
for _, v := range val {
|
||
req.Header.Set(key, v)
|
||
}
|
||
}
|
||
|
||
remoteAddr, err := resolveRemoteAddr(r.RemoteAddr, r.Context().Value(http.LocalAddrContextKey))
|
||
if err != nil {
|
||
remoteAddr = nil // Fallback to nil
|
||
}
|
||
|
||
// New fasthttp Ctx from pool
|
||
fctx := ctxPool.Get().(*fasthttp.RequestCtx) //nolint:forcetypeassert,errcheck // not needed
|
||
fctx.Response.Reset()
|
||
fctx.Request.Reset()
|
||
defer ctxPool.Put(fctx)
|
||
fctx.Init(req, remoteAddr, &disableLogger{})
|
||
|
||
if len(h) > 0 {
|
||
// New fiber Ctx
|
||
ctx := app.AcquireCtx(fctx)
|
||
defer app.ReleaseCtx(ctx)
|
||
|
||
// Execute fiber Ctx
|
||
err := h[0](ctx)
|
||
if err != nil {
|
||
_ = app.Config().ErrorHandler(ctx, err) //nolint:errcheck // not needed
|
||
}
|
||
} else {
|
||
// Execute fasthttp Ctx though app.Handler
|
||
app.Handler()(fctx)
|
||
}
|
||
|
||
// Convert fasthttp Ctx -> net/http
|
||
for k, v := range fctx.Response.Header.All() {
|
||
w.Header().Add(string(k), string(v))
|
||
}
|
||
w.WriteHeader(fctx.Response.StatusCode())
|
||
|
||
// Check if streaming is not possible or unnecessary.
|
||
bodyStream := fctx.Response.BodyStream()
|
||
flusher, ok := w.(http.Flusher)
|
||
if !ok || bodyStream == nil {
|
||
_, _ = w.Write(fctx.Response.Body()) //nolint:errcheck // not needed
|
||
return
|
||
}
|
||
|
||
// Stream fctx.Response.BodyStream() -> w
|
||
// in chunks.
|
||
bufPtr, ok := bufferPool.Get().(*[bufferSize]byte)
|
||
if !ok {
|
||
panic(fmt.Errorf("failed to type-assert to *[%d]byte", bufferSize))
|
||
}
|
||
defer bufferPool.Put(bufPtr)
|
||
|
||
buf := bufPtr[:]
|
||
for {
|
||
n, err := bodyStream.Read(buf)
|
||
if n > 0 {
|
||
if _, writeErr := w.Write(buf[:n]); writeErr != nil {
|
||
break
|
||
}
|
||
flusher.Flush()
|
||
}
|
||
|
||
if err != nil {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|