Skip to content

Commit 5314f45

Browse files
authored
Merge pull request #700 from projectdiscovery/dwisiswant0/fix/httputil/optimize-ResponseChain-memory-usage-to-prevent-OOM
fix(httputil): optimize `ResponseChain` memory usage
2 parents 5ef874c + d06a121 commit 5314f45

File tree

5 files changed

+1463
-37
lines changed

5 files changed

+1463
-37
lines changed

http/normalization.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,43 @@ import (
1818
stringsutil "github.com/projectdiscovery/utils/strings"
1919
)
2020

21+
// limitedBuffer wraps [bytes.Buffer] to prevent capacity growth beyond maxCap.
22+
// This prevents bytes.Buffer.ReadFrom() from over-allocating when it doesn't
23+
// know the final size.
24+
type limitedBuffer struct {
25+
buf *bytes.Buffer
26+
maxCap int
27+
}
28+
29+
func (lb *limitedBuffer) ReadFrom(r io.Reader) (n int64, err error) {
30+
const chunkSize = 32 * 1024 // 32KB chunks
31+
chunk := make([]byte, chunkSize)
32+
33+
for {
34+
available := lb.buf.Cap() - lb.buf.Len()
35+
if available < chunkSize && lb.buf.Cap() < lb.maxCap {
36+
needed := min(lb.buf.Len()+chunkSize, lb.maxCap)
37+
lb.buf.Grow(needed - lb.buf.Len())
38+
}
39+
40+
nr, readErr := r.Read(chunk)
41+
if nr > 0 {
42+
nw, writeErr := lb.buf.Write(chunk[:nr])
43+
n += int64(nw)
44+
if writeErr != nil {
45+
return n, writeErr
46+
}
47+
}
48+
49+
if readErr != nil {
50+
if readErr == io.EOF {
51+
return n, nil
52+
}
53+
return n, readErr
54+
}
55+
}
56+
}
57+
2158
// readNNormalizeRespBody performs normalization on the http response object.
2259
// and fills body buffer with actual response body.
2360
func readNNormalizeRespBody(rc *ResponseChain, body *bytes.Buffer) (err error) {
@@ -39,10 +76,13 @@ func readNNormalizeRespBody(rc *ResponseChain, body *bytes.Buffer) (err error) {
3976
if err != nil {
4077
wrapped = origBody
4178
}
42-
limitReader := io.LimitReader(wrapped, 2*MaxBodyRead)
79+
limitReader := io.LimitReader(wrapped, rc.maxBodySize)
4380

44-
// read response body to buffer
45-
_, err = body.ReadFrom(limitReader)
81+
// Read body using ReadFrom for efficiency, but cap growth at maxBodySize.
82+
// We use a custom limitedBuffer wrapper to prevent bytes.Buffer from
83+
// over-allocating (it normally grows to 2x when size is unknown).
84+
limitedBuf := &limitedBuffer{buf: body, maxCap: int(rc.maxBodySize)}
85+
_, err = limitedBuf.ReadFrom(limitReader)
4686
if err != nil {
4787
if strings.Contains(err.Error(), "gzip: invalid header") {
4888
// its invalid gzip but we will still use it from original body

0 commit comments

Comments
 (0)