Skip to content

Commit

Permalink
Restore the Response and Request when read from Transport
Browse files Browse the repository at this point in the history
Since the Request.Body and Response.Body are read from any transport.RoundTrip,
it is needed to restore them before reusing it again;
otherwise, the Request.Body or Response.Body won't be readable.

Signed-off-by: David Pordomingo <[email protected]>
  • Loading branch information
dpordomingo committed Oct 14, 2019
1 parent ab9d1d5 commit 789410c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 10 deletions.
15 changes: 8 additions & 7 deletions examples/cmd/logtransport.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package main

import (
"bytes"
"io/ioutil"
"net/http"
"time"

"gopkg.in/src-d/go-log.v1"

"github.com/src-d/metadata-retrieval/utils"
)

func setLogTransport(client *http.Client, logger log.Logger) {
Expand All @@ -21,8 +21,9 @@ type logTransport struct {
func (t *logTransport) RoundTrip(r *http.Request) (*http.Response, error) {
t0 := time.Now()

reqBody, _ := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
// Since the Request has been read, it is needed to restore it
// before reusing it; otherwise, its body will be unreadable again in other Transports.
reqBody, _ := utils.ReadRequestAndRestore(r)

resp, err := t.T.RoundTrip(r)
if err != nil {
Expand All @@ -32,7 +33,9 @@ func (t *logTransport) RoundTrip(r *http.Request) (*http.Response, error) {
return resp, err
}

respBody, err := ioutil.ReadAll(resp.Body)
// Since the Response has been read, it is needed to restore it
// before reusing it; otherwise, its body will be unreadable again in other Transports.
respBody, err := utils.ReadResponseAndRestore(resp)
if err != nil {
return resp, err
}
Expand All @@ -41,7 +44,5 @@ func (t *logTransport) RoundTrip(r *http.Request) (*http.Response, error) {
log.Fields{"elapsed": time.Since(t0), "response-code": resp.StatusCode, "url": r.URL, "request-body": string(reqBody), "response-body": string(respBody)},
).Debugf("HTTP response")

resp.Body = ioutil.NopCloser(bytes.NewBuffer(respBody))

return resp, err
}
2 changes: 1 addition & 1 deletion github/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func asErrRateLimit(resp *http.Response) *ErrRateLimit {

// readAPIErrorResponse reads the response.Body into the passed errorResponse
func readAPIErrorResponse(resp *http.Response, errorResponse *apiErrorResponse) error {
bodyContent, err := utils.BodyContentAndRestore(resp)
bodyContent, err := utils.ReadResponseAndRestore(resp)
if err != nil {
return err
}
Expand Down
8 changes: 8 additions & 0 deletions github/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"time"

"gopkg.in/src-d/go-log.v1"

"github.com/src-d/metadata-retrieval/utils"
)

// SetRetryTransport wraps the passed client.Transport with a RetryTransport
Expand All @@ -27,6 +29,12 @@ func (t *retryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return err
}

// Since the Request has been read, it is needed to restore it
// before reusing it; otherwise, its body will be unreadable again.
if _, err := utils.ReadRequestAndRestore(req); err != nil {
return nil
}

if r.StatusCode >= 500 {
return err
}
Expand Down
29 changes: 27 additions & 2 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package utils

import (
"bytes"
"io"
"io/ioutil"
"net/http"
)

// BodyContentAndRestore reads the content of the Response.Body, and restores it
// ReadResponseAndRestore reads the content of the Response.Body, and restores it
// to allow its reuse (e.g. when readen from Transport)
// https://github.com/google/go-github/pull/986
func BodyContentAndRestore(resp *http.Response) ([]byte, error) {
func ReadResponseAndRestore(resp *http.Response) ([]byte, error) {
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
Expand All @@ -19,3 +20,27 @@ func BodyContentAndRestore(resp *http.Response) ([]byte, error) {

return content, nil
}

// ReadRequestAndRestore reads the content of the Response.Body, and restores it
// to allow its reuse (e.g. when readen from Transport)
// https://github.com/google/go-github/pull/986
func ReadRequestAndRestore(req *http.Request) ([]byte, error) {
reader, content, err := ReadAndGetNew(req.Body)
if err != nil {
return nil, err
}

req.Body = reader
return content, nil
}

// ReadAndGetNew reads the content of the pased reader, and returns a brand new one
// with the same content with a no-op Close method
func ReadAndGetNew(reader io.Reader) (io.ReadCloser, []byte, error) {
content, err := ioutil.ReadAll(reader)
if err != nil {
return nil, nil, err
}

return ioutil.NopCloser(bytes.NewReader(content)), content, nil
}

0 comments on commit 789410c

Please sign in to comment.