Skip to content

Commit e2919d8

Browse files
committed
fix: utils StreamResponseBody() memory use for large get requests
The StreamResponseBody() called ctx.Write() in a loop with a small buffer in an attempt to stream data back to client. But the ctx.Write() was just calling append buffer to the response instead of streaming the data back to the client. The correct way to stream the response back is to use (ctx *fasthttp.RequestCtx).SetBodyStream() to set the body stream reader, and the response will automatically get streamed back using the reader. This will also call Close() on our body since we are providing an io.ReadCloser. Testing this should be done with single large get requests such as aws s3api get-object --bucket bucket --key file /tmp/data for very large objects. The testing shows significantly reduced memory usage for large objects once the streaming is enabled. Fixes #1082
1 parent 0d94d9a commit e2919d8

File tree

2 files changed

+10
-21
lines changed

2 files changed

+10
-21
lines changed

s3api/controllers/base.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,12 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
644644
}
645645

646646
if res.Body != nil {
647-
err := utils.StreamResponseBody(ctx, res.Body)
647+
// -1 will stream response body until EOF if content length not set
648+
contentLen := -1
649+
if res.ContentLength != nil {
650+
contentLen = int(*res.ContentLength)
651+
}
652+
err := utils.StreamResponseBody(ctx, res.Body, contentLen)
648653
if err != nil {
649654
SendResponse(ctx, nil,
650655
&MetaOpts{

s3api/utils/utils.go

+4-20
Original file line numberDiff line numberDiff line change
@@ -204,26 +204,10 @@ func SetResponseHeaders(ctx *fiber.Ctx, headers []CustomHeader) {
204204
}
205205

206206
// Streams the response body by chunks
207-
func StreamResponseBody(ctx *fiber.Ctx, rdr io.ReadCloser) error {
208-
buf := make([]byte, 4096) // 4KB chunks
209-
defer rdr.Close()
210-
for {
211-
n, err := rdr.Read(buf)
212-
if n > 0 {
213-
_, writeErr := ctx.Write(buf[:n])
214-
if writeErr != nil {
215-
return fmt.Errorf("write chunk: %w", writeErr)
216-
}
217-
}
218-
if err != nil {
219-
if errors.Is(err, io.EOF) {
220-
break
221-
}
222-
223-
return fmt.Errorf("read chunk: %w", err)
224-
}
225-
}
226-
207+
func StreamResponseBody(ctx *fiber.Ctx, rdr io.ReadCloser, bodysize int) error {
208+
// SetBodyStream will call Close() on the reader when the stream is done
209+
// since rdr is a ReadCloser
210+
ctx.Context().SetBodyStream(rdr, bodysize)
227211
return nil
228212
}
229213

0 commit comments

Comments
 (0)