Support huge read-only []byte response bodies (#477)

* Add `Response.SetBodyRaw` method that serves a `[]byte` slice without touching it  (as an alternative to `SetBody`)
* Update various response related functions that are impacted after the incoduction of `Response.bodyRaw`
* Add a few test-cases in relation to `Response.SetBodyRaw`
This commit is contained in:
Ciprian Dorin Craciun
2019-02-24 10:32:54 +02:00
committed by Erik Dubbelboer
parent ed3793a1e1
commit 733a6505a9
2 changed files with 58 additions and 1 deletions
+26 -1
View File
@@ -67,6 +67,7 @@ type Response struct {
bodyStream io.Reader
w responseBodyWriter
body *bytebufferpool.ByteBuffer
bodyRaw []byte
// Response.Read() skips reading body if set to true.
// Use it for reading HEAD responses.
@@ -321,6 +322,9 @@ func (resp *Response) Body() []byte {
}
func (resp *Response) bodyBytes() []byte {
if resp.bodyRaw != nil {
return resp.bodyRaw
}
if resp.body == nil {
return nil
}
@@ -338,6 +342,7 @@ func (resp *Response) bodyBuffer() *bytebufferpool.ByteBuffer {
if resp.body == nil {
resp.body = responseBodyPool.Get()
}
resp.bodyRaw = nil
return resp.body
}
@@ -462,6 +467,7 @@ func (resp *Response) SetBodyString(body string) {
// ResetBody resets response body.
func (resp *Response) ResetBody() {
resp.bodyRaw = nil
resp.closeBodyStream()
if resp.body != nil {
if resp.keepBodyBuffer {
@@ -473,6 +479,14 @@ func (resp *Response) ResetBody() {
}
}
// SetBodyRaw sets response body, but without copying it.
//
// From this point onward the body argument must not be changed.
func (resp *Response) SetBodyRaw(body []byte) {
resp.ResetBody()
resp.bodyRaw = body
}
// ReleaseBody retires the response body if it is greater than "size" bytes.
//
// This permits GC to reclaim the large buffer. If used, must be before
@@ -481,6 +495,7 @@ func (resp *Response) ResetBody() {
// Use this method only if you really understand how it works.
// The majority of workloads don't need this method.
func (resp *Response) ReleaseBody(size int) {
resp.bodyRaw = nil
if cap(resp.body.B) > size {
resp.closeBodyStream()
resp.body = nil
@@ -519,6 +534,8 @@ func (resp *Response) SwapBody(body []byte) []byte {
}
}
resp.bodyRaw = nil
oldBody := bb.B
bb.B = body
return oldBody
@@ -639,7 +656,12 @@ func (req *Request) copyToSkipBody(dst *Request) {
// CopyTo copies resp contents to dst except of body stream.
func (resp *Response) CopyTo(dst *Response) {
resp.copyToSkipBody(dst)
if resp.body != nil {
if resp.bodyRaw != nil {
dst.bodyRaw = resp.bodyRaw
if dst.body != nil {
dst.body.Reset()
}
} else if resp.body != nil {
dst.bodyBuffer().Set(resp.body.B)
} else if dst.body != nil {
dst.body.Reset()
@@ -661,6 +683,7 @@ func swapRequestBody(a, b *Request) {
func swapResponseBody(a, b *Response) {
a.body, b.body = b.body, a.body
a.bodyRaw, b.bodyRaw = b.bodyRaw, a.bodyRaw
a.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream
}
@@ -1265,6 +1288,7 @@ func (resp *Response) gzipBody(level int) error {
responseBodyPool.Put(resp.body)
}
resp.body = w
resp.bodyRaw = nil
}
resp.Header.SetCanonical(strContentEncoding, strGzip)
return nil
@@ -1319,6 +1343,7 @@ func (resp *Response) deflateBody(level int) error {
responseBodyPool.Put(resp.body)
}
resp.body = w
resp.bodyRaw = nil
}
resp.Header.SetCanonical(strContentEncoding, strDeflate)
return nil
+32
View File
@@ -1891,3 +1891,35 @@ Content-Type: application/json
t.Fatalf("unexpected output %q", w.Bytes())
}
}
func TestResponseRawBodySet(t *testing.T) {
var resp Response
expectedS := "test"
body := []byte(expectedS)
resp.SetBodyRaw(body)
testBodyWriteTo(t, &resp, expectedS, true)
}
func TestResponseRawBodyReset(t *testing.T) {
var resp Response
body := []byte("test")
resp.SetBodyRaw(body)
resp.ResetBody()
testBodyWriteTo(t, &resp, "", true)
}
func TestResponseRawBodyCopyTo(t *testing.T) {
var resp Response
expectedS := "test"
body := []byte(expectedS)
resp.SetBodyRaw(body)
testResponseCopyTo(t, &resp)
}