mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-11-09 08:54:27 +00:00
fix(coalesce): copy the primary response body for waiters
Waiters read the primary response buffer while fasthttp could reuse it for another request. A waiter could then send the wrong body. The primary now gives waiters their own copy.
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pins theorems/Theorems/CoalesceAlias.lean: waiters never alias the primary's response buffer.
|
||||||
|
func TestSnapshotCoalescedResponse_OwnsBody(t *testing.T) {
|
||||||
|
app := fiber.New()
|
||||||
|
fctx := &fasthttp.RequestCtx{}
|
||||||
|
c := app.AcquireCtx(fctx)
|
||||||
|
defer app.ReleaseCtx(c)
|
||||||
|
|
||||||
|
c.Response().SetStatusCode(201)
|
||||||
|
c.Response().SetBody([]byte(`{"user":"alice"}`))
|
||||||
|
|
||||||
|
snap := snapshotCoalescedResponse(c)
|
||||||
|
|
||||||
|
c.Response().SetBody([]byte(`{"user":"bobby"}`)) // buffer reused by a later request
|
||||||
|
|
||||||
|
assert.Equal(t, 201, snap.StatusCode)
|
||||||
|
assert.Equal(t, `{"user":"alice"}`, string(snap.Body))
|
||||||
|
}
|
||||||
@@ -740,11 +740,7 @@ func performProxyRequest(c fiber.Ctx, proxyURL string) error {
|
|||||||
}, proxyErr
|
}, proxyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CoalescedResponse{
|
return snapshotCoalescedResponse(c), nil
|
||||||
Body: c.Response().Body(),
|
|
||||||
StatusCode: c.Response().StatusCode(),
|
|
||||||
// Headers intentionally left nil; not populated or read anywhere.
|
|
||||||
}, nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check for error from rc.Do (though it typically returns nil)
|
// Check for error from rc.Do (though it typically returns nil)
|
||||||
@@ -773,6 +769,17 @@ func performProxyRequest(c fiber.Ctx, proxyURL string) error {
|
|||||||
return performProxyRequestCore(c, proxyURL, cacheKey)
|
return performProxyRequestCore(c, proxyURL, cacheKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// snapshotCoalescedResponse captures the primary's response for waiters. It
|
||||||
|
// copies the body because fasthttp reuses the buffer once the primary handler
|
||||||
|
// returns, while waiters can still be reading.
|
||||||
|
func snapshotCoalescedResponse(c fiber.Ctx) *CoalescedResponse {
|
||||||
|
return &CoalescedResponse{
|
||||||
|
Body: bytes.Clone(c.Response().Body()),
|
||||||
|
StatusCode: c.Response().StatusCode(),
|
||||||
|
// Headers intentionally left nil; not populated or read anywhere.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// performProxyRequestCore executes the proxy request with retries and circuit breaker
|
// performProxyRequestCore executes the proxy request with retries and circuit breaker
|
||||||
// This is the core implementation used by both direct calls and coalesced requests
|
// This is the core implementation used by both direct calls and coalesced requests
|
||||||
func performProxyRequestCore(c fiber.Ctx, proxyURL string, cacheKey string) error {
|
func performProxyRequestCore(c fiber.Ctx, proxyURL string, cacheKey string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user