Skip to content
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

v3: Improve performance of Adaptor Middleware #3078

Merged
merged 4 commits into from
Jul 18, 2024
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
28 changes: 28 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,34 @@ DRAFT section

## 🧬 Middlewares

### Adaptor

The adaptor middleware has been significantly optimized for performance and efficiency. Key improvements include reduced response times, lower memory usage, and fewer memory allocations. These changes make the middleware more reliable and capable of handling higher loads effectively. Enhancements include the introduction of a `sync.Pool` for managing `fasthttp.RequestCtx` instances and better HTTP request and response handling between net/http and fasthttp contexts.

| Payload Size | Metric | V2 | V3 | Percent Change |
|--------------|------------------|-----------|----------|-------------------|
| 100KB | Execution Time | 1056 ns/op| 588.6 ns/op | -44.25% |
| | Memory Usage | 2644 B/op | 254 B/op | -90.39% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 500KB | Execution Time | 1061 ns/op| 562.9 ns/op | -46.94% |
| | Memory Usage | 2644 B/op | 248 B/op | -90.62% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 1MB | Execution Time | 1080 ns/op| 629.7 ns/op | -41.68% |
| | Memory Usage | 2646 B/op | 267 B/op | -89.91% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 5MB | Execution Time | 1093 ns/op| 540.3 ns/op | -50.58% |
| | Memory Usage | 2654 B/op | 254 B/op | -90.43% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 10MB | Execution Time | 1044 ns/op| 533.1 ns/op | -48.94% |
| | Memory Usage | 2665 B/op | 258 B/op | -90.32% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 25MB | Execution Time | 1069 ns/op| 540.7 ns/op | -49.42% |
| | Memory Usage | 2706 B/op | 289 B/op | -89.32% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 50MB | Execution Time | 1137 ns/op| 554.6 ns/op | -51.21% |
| | Memory Usage | 2734 B/op | 298 B/op | -89.10% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |

### Cache

We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define a custom conditions for invalidating cache entries.
Expand Down
31 changes: 24 additions & 7 deletions middleware/adaptor/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net"
"net/http"
"reflect"
"sync"
"unsafe"

"github.com/gofiber/fiber/v3"
Expand All @@ -13,6 +14,12 @@ import (
"github.com/valyala/fasthttp/fasthttpadaptor"
)

var ctxPool = sync.Pool{
New: func() any {
return new(fasthttp.RequestCtx)
},
}

// HTTPHandlerFunc wraps net/http handler func to fiber handler
func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler {
return HTTPHandler(h)
Expand Down Expand Up @@ -82,12 +89,13 @@ 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
// Convert again in case request may modify by middleware
next = true
c.Request().Header.SetMethod(r.Method)
c.Request().SetRequestURI(r.RequestURI)
c.Request().SetHost(r.Host)
c.Request().Header.SetHost(r.Host)

for key, val := range r.Header {
for _, v := range val {
c.Request().Header.Set(key, v)
Expand Down Expand Up @@ -124,9 +132,9 @@ func FiberApp(app *fiber.App) http.HandlerFunc {

func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// New fasthttp request
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)

// Convert net/http -> fasthttp request
if r.Body != nil {
n, err := io.Copy(req.BodyWriter(), r.Body)
Expand All @@ -141,37 +149,46 @@ func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
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)
}
}

if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil && err.(*net.AddrError).Err == "missing port in address" { //nolint:errorlint, forcetypeassert // overlinting
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
}

remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil {
http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError)
return
}

// New fasthttp Ctx
var fctx fasthttp.RequestCtx
// New fasthttp Ctx from pool
fctx := ctxPool.Get().(*fasthttp.RequestCtx) //nolint:forcetypeassert,errcheck // overlinting
fctx.Response.Reset()
fctx.Request.Reset()
defer ctxPool.Put(fctx)
fctx.Init(req, remoteAddr, nil)

if len(h) > 0 {
// New fiber Ctx
ctx := app.AcquireCtx(&fctx)
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)
app.Handler()(fctx)
}

// Convert fasthttp Ctx > net/http
// Convert fasthttp Ctx -> net/http
fctx.Response.Header.VisitAll(func(k, v []byte) {
w.Header().Add(string(k), string(v))
})
Expand Down
Loading