diff --git a/client.go b/client.go index 36b9f9ce..55709b47 100644 --- a/client.go +++ b/client.go @@ -869,7 +869,6 @@ func (c *Client) GetClient() *http.Client { // Executes method executes the given `Request` object and returns response // error. func (c *Client) execute(req *Request) (*Response, error) { - defer releaseBuffer(req.bodyBuf) // Apply Request middleware var err error @@ -903,6 +902,8 @@ func (c *Client) execute(req *Request) (*Response, error) { return nil, wrapNoRetryErr(err) } + req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf) + req.Time = time.Now() resp, err := c.httpClient.Do(req.RawRequest) diff --git a/middleware.go b/middleware.go index 06ed48b5..7461c806 100644 --- a/middleware.go +++ b/middleware.go @@ -458,7 +458,7 @@ func handleRequestBody(c *Client, r *Request) (err error) { bodyBytes = []byte(s) } else if IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) { - bodyBytes, err = jsonMarshal(c, r, r.Body) + r.bodyBuf, err = jsonMarshal(c, r, r.Body) if err != nil { return } diff --git a/request.go b/request.go index b9d01847..bf857e57 100644 --- a/request.go +++ b/request.go @@ -870,11 +870,14 @@ func (r *Request) initValuesMap() { } } -var noescapeJSONMarshal = func(v interface{}) ([]byte, error) { +var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) { buf := acquireBuffer() - defer releaseBuffer(buf) encoder := json.NewEncoder(buf) encoder.SetEscapeHTML(false) - err := encoder.Encode(v) - return buf.Bytes(), err + if err := encoder.Encode(v); err != nil { + releaseBuffer(buf) + return nil, err + } + + return buf, nil } diff --git a/util.go b/util.go index b0172560..d0abb4e7 100644 --- a/util.go +++ b/util.go @@ -19,6 +19,7 @@ import ( "runtime" "sort" "strings" + "sync" ) //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ @@ -139,13 +140,19 @@ type ResponseLog struct { //_______________________________________________________________________ // way to disable the HTML escape as opt-in -func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) { - if !r.jsonEscapeHTML { - return noescapeJSONMarshal(d) - } else if !c.jsonEscapeHTML { +func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) { + if !r.jsonEscapeHTML || !c.jsonEscapeHTML { return noescapeJSONMarshal(d) } - return c.JSONMarshal(d) + + data, err := c.JSONMarshal(d) + if err != nil { + return nil, err + } + + buf := acquireBuffer() + _, _ = buf.Write(data) + return buf, nil } func firstNonEmpty(v ...string) string { @@ -283,6 +290,34 @@ func releaseBuffer(buf *bytes.Buffer) { } } +// requestBodyReleaser wraps requests's body and implements custom Close for it. +// The Close method closes original body and releases request body back to sync.Pool. +type requestBodyReleaser struct { + releaseOnce sync.Once + reqBuf *bytes.Buffer + io.ReadCloser +} + +func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser { + if reqBuf == nil { + return respBody + } + + return &requestBodyReleaser{ + reqBuf: reqBuf, + ReadCloser: respBody, + } +} + +func (rr *requestBodyReleaser) Close() error { + err := rr.ReadCloser.Close() + rr.releaseOnce.Do(func() { + releaseBuffer(rr.reqBuf) + }) + + return err +} + func closeq(v interface{}) { if c, ok := v.(io.Closer); ok { silently(c.Close())