mirror of
https://github.com/versity/versitygw.git
synced 2026-01-08 04:35:15 +00:00
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
This commit is contained in:
@@ -644,16 +644,12 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
err := utils.StreamResponseBody(ctx, res.Body)
|
||||
if err != nil {
|
||||
SendResponse(ctx, nil,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
MetricsMng: c.mm,
|
||||
Action: metrics.ActionGetObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
// -1 will stream response body until EOF if content length not set
|
||||
contentLen := -1
|
||||
if res.ContentLength != nil {
|
||||
contentLen = int(*res.ContentLength)
|
||||
}
|
||||
utils.StreamResponseBody(ctx, res.Body, contentLen)
|
||||
}
|
||||
|
||||
return SendResponse(ctx, nil,
|
||||
|
||||
@@ -204,27 +204,10 @@ func SetResponseHeaders(ctx *fiber.Ctx, headers []CustomHeader) {
|
||||
}
|
||||
|
||||
// Streams the response body by chunks
|
||||
func StreamResponseBody(ctx *fiber.Ctx, rdr io.ReadCloser) error {
|
||||
buf := make([]byte, 4096) // 4KB chunks
|
||||
defer rdr.Close()
|
||||
for {
|
||||
n, err := rdr.Read(buf)
|
||||
if n > 0 {
|
||||
_, writeErr := ctx.Write(buf[:n])
|
||||
if writeErr != nil {
|
||||
return fmt.Errorf("write chunk: %w", writeErr)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return fmt.Errorf("read chunk: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func StreamResponseBody(ctx *fiber.Ctx, rdr io.ReadCloser, bodysize int) {
|
||||
// SetBodyStream will call Close() on the reader when the stream is done
|
||||
// since rdr is a ReadCloser
|
||||
ctx.Context().SetBodyStream(rdr, bodysize)
|
||||
}
|
||||
|
||||
func IsValidBucketName(bucket string) bool {
|
||||
|
||||
Reference in New Issue
Block a user