From 789410c63f84055aab6742efba54944481a83ae4 Mon Sep 17 00:00:00 2001 From: David Pordomingo Date: Mon, 14 Oct 2019 15:14:19 +0200 Subject: [PATCH] Restore the Response and Request when read from Transport 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 --- examples/cmd/logtransport.go | 15 ++++++++------- github/ratelimit.go | 2 +- github/retry.go | 8 ++++++++ utils/utils.go | 29 +++++++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/examples/cmd/logtransport.go b/examples/cmd/logtransport.go index 343c75b..ea00836 100644 --- a/examples/cmd/logtransport.go +++ b/examples/cmd/logtransport.go @@ -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) { @@ -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 { @@ -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 } @@ -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 } diff --git a/github/ratelimit.go b/github/ratelimit.go index 90a21b0..342f846 100644 --- a/github/ratelimit.go +++ b/github/ratelimit.go @@ -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 } diff --git a/github/retry.go b/github/retry.go index e003999..be87837 100644 --- a/github/retry.go +++ b/github/retry.go @@ -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 @@ -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 } diff --git a/utils/utils.go b/utils/utils.go index a53020a..7673ed7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -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 @@ -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 +}