From e16f958491054beac9d5fb81027c3b3744d92978 Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Mon, 9 Aug 2021 13:20:47 -0400 Subject: [PATCH 01/20] support HTTPDate in Retry-After header According to [RFC7231](https://httpwg.org/specs/rfc7231.html#header.retry-after) the Retry-After header may be specified in both number of seconds OR an HTTPDate > The value of this field can be either an HTTP-date or a number of seconds to delay after the response is received. > > Retry-After = HTTP-date / delay-seconds > > A delay-seconds value is a non-negative decimal integer, representing time in seconds. > > delay-seconds = 1*DIGIT > > Two examples of its use are > > Retry-After: Fri, 31 Dec 1999 23:59:59 GMT > Retry-After: 120 This change adds support for parsing the date if the format of the header contains non-numeric characters --- client.go | 47 +++++++++++++++++++++++++++++++++++++++++++---- client_test.go | 28 ++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index adbdd92..457f930 100644 --- a/client.go +++ b/client.go @@ -69,6 +69,14 @@ var ( // scheme specified in the URL is invalid. This error isn't typed // specifically so we resort to matching on the error string. schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`) + + // A regular expression to match the number of seconds to delay + // when parsing the Retry-After header + retryAfterSecondsRe = regexp.MustCompile(`^[0-9]+$`) + + // timeNow sets the function that returns the current time. + // This defaults to time.Now. Changes to this should only be done in tests. + timeNow = time.Now ) // ReaderFunc is the type of function that can be given natively to NewRequest @@ -472,10 +480,8 @@ func baseRetryPolicy(resp *http.Response, err error) (bool, error) { func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { if resp != nil { if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable { - if s, ok := resp.Header["Retry-After"]; ok { - if sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil { - return time.Second * time.Duration(sleep) - } + if sleep, ok := parseRetryAfterHeader(resp.Header["Retry-After"]); ok { + return sleep } } } @@ -488,6 +494,39 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) return sleep } +// parseRetryAfterHeader parses the Retry-After header and returns the +// delay duration according to the spec: https://httpwg.org/specs/rfc7231.html#header.retry-after +// +// Retry-After headers come in two flavors: Seconds or HTTP-Date +// +// Examples: +// * Retry-After: Fri, 31 Dec 1999 23:59:59 GMT +// * Retry-After: 120 +func parseRetryAfterHeader(headers []string) (time.Duration, bool) { + if len(headers) == 0 || headers[0] == "" { + return 0, false + } + header := headers[0] + // Retry-After: 120 + if retryAfterSecondsRe.MatchString(header) { + if sleep, err := strconv.ParseInt(header, 10, 64); err == nil { + return time.Second * time.Duration(sleep), true + } + return 0, false + } + + // Retry-After: Mon, 02 Jan 2006 15:04:05 MST + retryTime, err := time.Parse(time.RFC1123, header) + if err != nil { + return 0, false + } + if until := retryTime.Sub(timeNow()); until > 0 { + return until, true + } + // date is in the past + return 0, true +} + // LinearJitterBackoff provides a callback for Client.Backoff which will // perform linear backoff based on the attempt number and with jitter to // prevent a thundering herd. diff --git a/client_test.go b/client_test.go index 082b407..401b5ea 100644 --- a/client_test.go +++ b/client_test.go @@ -524,11 +524,31 @@ func TestClient_CheckRetry(t *testing.T) { } func TestClient_DefaultBackoff(t *testing.T) { - for _, code := range []int{http.StatusTooManyRequests, http.StatusServiceUnavailable} { - t.Run(fmt.Sprintf("http_%d", code), func(t *testing.T) { + timeNow = func() time.Time { + now, err := time.Parse(time.RFC1123, "Fri, 31 Dec 1999 23:59:57 GMT") + if err != nil { + panic(err) + } + return now + } + defer func() { + timeNow = time.Now + }() + tests := []struct { + name string + code int + retryHeader string + }{ + {"http_429_seconds", http.StatusTooManyRequests, "2"}, + {"http_429_date", http.StatusTooManyRequests, "Fri, 31 Dec 1999 23:59:59 GMT"}, + {"http_503_seconds", http.StatusServiceUnavailable, "2"}, + {"http_503_date", http.StatusTooManyRequests, "Fri, 31 Dec 1999 23:59:59 GMT"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Retry-After", "2") - http.Error(w, fmt.Sprintf("test_%d_body", code), code) + w.Header().Set("Retry-After", test.retryHeader) + http.Error(w, fmt.Sprintf("test_%d_body", test.code), test.code) })) defer ts.Close() From 0dc51cc2ee4ea10708cb9dad85141c7c3a83c37d Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Fri, 13 Aug 2021 11:40:56 -0400 Subject: [PATCH 02/20] tests: fix DefaultBackoff case --- client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index 401b5ea..063f258 100644 --- a/client_test.go +++ b/client_test.go @@ -542,7 +542,7 @@ func TestClient_DefaultBackoff(t *testing.T) { {"http_429_seconds", http.StatusTooManyRequests, "2"}, {"http_429_date", http.StatusTooManyRequests, "Fri, 31 Dec 1999 23:59:59 GMT"}, {"http_503_seconds", http.StatusServiceUnavailable, "2"}, - {"http_503_date", http.StatusTooManyRequests, "Fri, 31 Dec 1999 23:59:59 GMT"}, + {"http_503_date", http.StatusServiceUnavailable, "Fri, 31 Dec 1999 23:59:59 GMT"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From 0ed3cd7540edad37b50599779c656c6340ffc7a3 Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Fri, 13 Aug 2021 11:41:37 -0400 Subject: [PATCH 03/20] tests: helper to set static time.Now --- client_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client_test.go b/client_test.go index 063f258..e14e606 100644 --- a/client_test.go +++ b/client_test.go @@ -523,7 +523,7 @@ func TestClient_CheckRetry(t *testing.T) { } } -func TestClient_DefaultBackoff(t *testing.T) { +func testStaticTime(t *testing.T) { timeNow = func() time.Time { now, err := time.Parse(time.RFC1123, "Fri, 31 Dec 1999 23:59:57 GMT") if err != nil { @@ -531,9 +531,13 @@ func TestClient_DefaultBackoff(t *testing.T) { } return now } - defer func() { + t.Cleanup(func() { timeNow = time.Now - }() + }) +} + +func TestClient_DefaultBackoff(t *testing.T) { + testStaticTime(t) tests := []struct { name string code int From 012f144018e17693cf92c653a60f7060f262e20d Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Fri, 13 Aug 2021 11:42:17 -0400 Subject: [PATCH 04/20] tests: add test cases for parseRetryAfterHeader --- client_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/client_test.go b/client_test.go index e14e606..9fdb758 100644 --- a/client_test.go +++ b/client_test.go @@ -536,6 +536,37 @@ func testStaticTime(t *testing.T) { }) } +func TestParseRetryAfterHeader(t *testing.T) { + testStaticTime(t) + tests := []struct { + name string + headers []string + sleep time.Duration + ok bool + }{ + {"seconds", []string{"2"}, time.Second * 2, true}, + {"date", []string{"Fri, 31 Dec 1999 23:59:59 GMT"}, time.Second * 2, true}, + {"past-date", []string{"Fri, 31 Dec 1999 23:59:00 GMT"}, 0, true}, + {"nil", nil, 0, false}, + {"two-headers", []string{"2", "3"}, time.Second * 2, true}, + {"empty", []string{""}, 0, false}, + {"negative", []string{"-2"}, 0, false}, + {"bad-date", []string{"Fri, 32 Dec 1999 23:59:59 GMT"}, 0, false}, + {"bad-date-format", []string{"badbadbad"}, 0, false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sleep, ok := parseRetryAfterHeader(test.headers) + if ok != test.ok { + t.Fatalf("expected ok=%t, got ok=%t", test.ok, ok) + } + if sleep != test.sleep { + t.Fatalf("expected sleep=%v, got sleep=%v", test.sleep, sleep) + } + }) + } +} + func TestClient_DefaultBackoff(t *testing.T) { testStaticTime(t) tests := []struct { From f2396f056078c3077dd837617ce8ba7ca7c4413d Mon Sep 17 00:00:00 2001 From: Justen Walker Date: Fri, 13 Aug 2021 11:42:47 -0400 Subject: [PATCH 05/20] remove regexp for parseRetryAfterHeader Removes dependency on regular expressions for detecting numeric header values vs date values --- client.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/client.go b/client.go index 457f930..3b104be 100644 --- a/client.go +++ b/client.go @@ -70,10 +70,6 @@ var ( // specifically so we resort to matching on the error string. schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`) - // A regular expression to match the number of seconds to delay - // when parsing the Retry-After header - retryAfterSecondsRe = regexp.MustCompile(`^[0-9]+$`) - // timeNow sets the function that returns the current time. // This defaults to time.Now. Changes to this should only be done in tests. timeNow = time.Now @@ -496,6 +492,8 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) // parseRetryAfterHeader parses the Retry-After header and returns the // delay duration according to the spec: https://httpwg.org/specs/rfc7231.html#header.retry-after +// The bool returned will be true if the header was successfully parsed. +// Otherwise, the header was either not present, or was not parseable according to the spec. // // Retry-After headers come in two flavors: Seconds or HTTP-Date // @@ -508,14 +506,14 @@ func parseRetryAfterHeader(headers []string) (time.Duration, bool) { } header := headers[0] // Retry-After: 120 - if retryAfterSecondsRe.MatchString(header) { - if sleep, err := strconv.ParseInt(header, 10, 64); err == nil { - return time.Second * time.Duration(sleep), true + if sleep, err := strconv.ParseInt(header, 10, 64); err == nil { + if sleep < 0 { // a negative sleep doesn't make sense + return 0, false } - return 0, false + return time.Second * time.Duration(sleep), true } - // Retry-After: Mon, 02 Jan 2006 15:04:05 MST + // Retry-After: Fri, 31 Dec 1999 23:59:59 GMT retryTime, err := time.Parse(time.RFC1123, header) if err != nil { return 0, false From 676f2f06de023ec306b2c6f0fced21af2d192d9a Mon Sep 17 00:00:00 2001 From: Marko Kevac Date: Thu, 10 Feb 2022 15:28:59 +0300 Subject: [PATCH 06/20] Change URL from godoc to pkg.go.dev in README.md Change URL from godoc to pkg.go.dev in README.md as godoc is not supported anymore. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8943bec..145a62f 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,4 @@ standardClient := retryClient.StandardClient() // *http.Client ``` For more usage and examples see the -[godoc](http://godoc.org/github.com/hashicorp/go-retryablehttp). +[pkg.go.dev](https://pkg.go.dev/github.com/hashicorp/go-retryablehttp). From 3899851f2b38c8a02e3fce600e3539ec8a26e09a Mon Sep 17 00:00:00 2001 From: longshine Date: Fri, 19 May 2023 16:53:12 +0800 Subject: [PATCH 07/20] Avoid read all from bytes.Reader when get request body --- client.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 7f482af..c345af6 100644 --- a/client.go +++ b/client.go @@ -234,14 +234,12 @@ func getBodyReaderAndContentLength(rawBody interface{}) (ReaderFunc, int64, erro // deal with it seeking so want it to match here instead of the // io.ReadSeeker case. case *bytes.Reader: - buf, err := ioutil.ReadAll(body) - if err != nil { - return nil, 0, err - } + snapshot := *body bodyReader = func() (io.Reader, error) { - return bytes.NewReader(buf), nil + r := snapshot + return &r, nil } - contentLength = int64(len(buf)) + contentLength = int64(body.Len()) // Compat case case io.ReadSeeker: From c872c98a89ad5ee0c84b2c3250716901fd81aecf Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Mon, 5 Feb 2024 14:58:56 +0100 Subject: [PATCH 08/20] DXE-3480 Re-sign request on retry --- client.go | 18 ++++++++++++++++++ go.mod | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index c9edbd0..0a7c9dc 100644 --- a/client.go +++ b/client.go @@ -393,6 +393,9 @@ type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) t // attempted. If overriding this, be sure to close the body if needed. type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Response, error) +// Postprocess is called before retry operation. It can be used to for example re-sign the request +type Postprocess func(req *http.Request) error + // Client is used to make HTTP requests. It adds additional functionality // like automatic retries to tolerate minor outages. type Client struct { @@ -421,6 +424,9 @@ type Client struct { // ErrorHandler specifies the custom error handler to use, if any ErrorHandler ErrorHandler + // Postprocess can prepare the request for retry operation, for example re-sign it + Postprocess Postprocess + loggerInit sync.Once clientInit sync.Once } @@ -435,6 +441,7 @@ func NewClient() *Client { RetryMax: defaultRetryMax, CheckRetry: DefaultRetryPolicy, Backoff: DefaultBackoff, + Postprocess: DefaultPostprocess, } } @@ -551,6 +558,12 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) return sleep } +// DefaultPostprocess is performing noop during postprocess +func DefaultPostprocess(_ *http.Request) error { + // noop + return nil +} + // LinearJitterBackoff provides a callback for Client.Backoff which will // perform linear backoff based on the attempt number and with jitter to // prevent a thundering herd. @@ -728,6 +741,11 @@ func (c *Client) Do(req *Request) (*http.Response, error) { // without racing against the closeBody call in persistConn.writeLoop. httpreq := *req.Request req.Request = &httpreq + + if err := c.Postprocess(req.Request); err != nil { + checkErr = err + break + } } // this is the closest we have to success criteria diff --git a/go.mod b/go.mod index d05df1b..9257b1e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hashicorp/go-retryablehttp +module github.com/mgwoj/go-retryablehttp require ( github.com/hashicorp/go-cleanhttp v0.5.2 From b2e8d9b4aee31e67ab6505f4633082ba4dd08075 Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Fri, 16 Feb 2024 13:28:27 +0100 Subject: [PATCH 09/20] DXE-3480 Configurable retry --- client.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index 0a7c9dc..c8fe8f4 100644 --- a/client.go +++ b/client.go @@ -393,8 +393,8 @@ type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) t // attempted. If overriding this, be sure to close the body if needed. type ErrorHandler func(resp *http.Response, err error, numTries int) (*http.Response, error) -// Postprocess is called before retry operation. It can be used to for example re-sign the request -type Postprocess func(req *http.Request) error +// PrepareRetry is called before retry operation. It can be used for example to re-sign the request +type PrepareRetry func(req *http.Request) error // Client is used to make HTTP requests. It adds additional functionality // like automatic retries to tolerate minor outages. @@ -424,8 +424,8 @@ type Client struct { // ErrorHandler specifies the custom error handler to use, if any ErrorHandler ErrorHandler - // Postprocess can prepare the request for retry operation, for example re-sign it - Postprocess Postprocess + // PrepareRetry can prepare the request for retry operation, for example re-sign it + PrepareRetry PrepareRetry loggerInit sync.Once clientInit sync.Once @@ -441,7 +441,7 @@ func NewClient() *Client { RetryMax: defaultRetryMax, CheckRetry: DefaultRetryPolicy, Backoff: DefaultBackoff, - Postprocess: DefaultPostprocess, + PrepareRetry: DefaultPrepareRetry, } } @@ -558,8 +558,8 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) return sleep } -// DefaultPostprocess is performing noop during postprocess -func DefaultPostprocess(_ *http.Request) error { +// DefaultPrepareRetry is performing noop during prepare retry +func DefaultPrepareRetry(_ *http.Request) error { // noop return nil } @@ -631,10 +631,10 @@ func (c *Client) Do(req *Request) (*http.Response, error) { var resp *http.Response var attempt int var shouldRetry bool - var doErr, respErr, checkErr error + var doErr, respErr, checkErr, prepareErr error for i := 0; ; i++ { - doErr, respErr = nil, nil + doErr, respErr, prepareErr = nil, nil, nil attempt++ // Always rewind the request body when non-nil. @@ -742,21 +742,23 @@ func (c *Client) Do(req *Request) (*http.Response, error) { httpreq := *req.Request req.Request = &httpreq - if err := c.Postprocess(req.Request); err != nil { - checkErr = err + if err := c.PrepareRetry(req.Request); err != nil { + prepareErr = err break } } // this is the closest we have to success criteria - if doErr == nil && respErr == nil && checkErr == nil && !shouldRetry { + if doErr == nil && respErr == nil && checkErr == nil && prepareErr == nil && !shouldRetry { return resp, nil } defer c.HTTPClient.CloseIdleConnections() var err error - if checkErr != nil { + if prepareErr != nil { + err = prepareErr + } else if checkErr != nil { err = checkErr } else if respErr != nil { err = respErr From 25093acfa1aeefdda6357469f760700bb0a7a6c6 Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Fri, 8 Mar 2024 11:32:52 +0100 Subject: [PATCH 10/20] DXE-3480 Unit test --- client_test.go | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/client_test.go b/client_test.go index c5e98a5..a751d3f 100644 --- a/client_test.go +++ b/client_test.go @@ -15,6 +15,7 @@ import ( "net/http/httptest" "net/http/httputil" "net/url" + "strconv" "strings" "sync/atomic" "testing" @@ -352,6 +353,128 @@ func TestClient_Do_WithResponseHandler(t *testing.T) { } } +func TestClient_Do_WithPrepareRetry(t *testing.T) { + // Create the client. Use short retry windows so we fail faster. + client := NewClient() + client.RetryWaitMin = 10 * time.Millisecond + client.RetryWaitMax = 10 * time.Millisecond + client.RetryMax = 2 + + var checks int + client.CheckRetry = func(_ context.Context, resp *http.Response, err error) (bool, error) { + checks++ + if err != nil && strings.Contains(err.Error(), "nonretryable") { + return false, nil + } + return DefaultRetryPolicy(context.TODO(), resp, err) + } + + var prepareChecks int + client.PrepareRetry = func(req *http.Request) error { + prepareChecks++ + req.Header.Set("foo", strconv.Itoa(prepareChecks)) + return nil + } + + // Mock server which always responds 200. + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + })) + defer ts.Close() + + var shouldSucceed bool + tests := []struct { + name string + handler ResponseHandlerFunc + expectedChecks int // often 2x number of attempts since we check twice + expectedPrepareChecks int + err string + }{ + { + name: "nil handler", + handler: nil, + expectedChecks: 1, + expectedPrepareChecks: 0, + }, + { + name: "handler always succeeds", + handler: func(*http.Response) error { + return nil + }, + expectedChecks: 2, + expectedPrepareChecks: 0, + }, + { + name: "handler always fails in a retryable way", + handler: func(*http.Response) error { + return errors.New("retryable failure") + }, + expectedChecks: 6, + expectedPrepareChecks: 2, + }, + { + name: "handler always fails in a nonretryable way", + handler: func(*http.Response) error { + return errors.New("nonretryable failure") + }, + expectedChecks: 2, + expectedPrepareChecks: 0, + }, + { + name: "handler succeeds on second attempt", + handler: func(*http.Response) error { + if shouldSucceed { + return nil + } + shouldSucceed = true + return errors.New("retryable failure") + }, + expectedChecks: 4, + expectedPrepareChecks: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checks = 0 + prepareChecks = 0 + shouldSucceed = false + // Create the request + req, err := NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + req.SetResponseHandler(tt.handler) + + // Send the request. + _, err = client.Do(req) + if err != nil && !strings.Contains(err.Error(), tt.err) { + t.Fatalf("error does not match expectation, expected: %s, got: %s", tt.err, err.Error()) + } + if err == nil && tt.err != "" { + t.Fatalf("no error, expected: %s", tt.err) + } + + if checks != tt.expectedChecks { + t.Fatalf("expected %d attempts, got %d attempts", tt.expectedChecks, checks) + } + + if prepareChecks != tt.expectedPrepareChecks { + t.Fatalf("expected %d attempts of prepare check, got %d attempts", tt.expectedPrepareChecks, prepareChecks) + } + header := req.Request.Header.Get("foo") + if tt.expectedPrepareChecks == 0 && header != "" { + t.Fatalf("expected no changes to request header 'foo', but got '%s'", header) + } + expectedHeader := strconv.Itoa(tt.expectedPrepareChecks) + if tt.expectedPrepareChecks != 0 && header != expectedHeader { + t.Fatalf("expected changes in request header 'foo' '%s', but got '%s'", expectedHeader, header) + } + + }) + } +} + func TestClient_Do_fails(t *testing.T) { // Mock server which always responds 500. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From c5939ed658c9297bbff723e81b1d3712cf668484 Mon Sep 17 00:00:00 2001 From: Michal Wojcik <32574975+mgwoj@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:44:31 +0100 Subject: [PATCH 11/20] Update go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9257b1e..d05df1b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/mgwoj/go-retryablehttp +module github.com/hashicorp/go-retryablehttp require ( github.com/hashicorp/go-cleanhttp v0.5.2 From edadfe12db0d0c8b117a497a283a199cdb6627f1 Mon Sep 17 00:00:00 2001 From: Michal Wojcik Date: Fri, 26 Apr 2024 09:04:04 +0200 Subject: [PATCH 12/20] DXE-3480 Simplify code for PrepareRetry --- client.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/client.go b/client.go index c8fe8f4..9b09c21 100644 --- a/client.go +++ b/client.go @@ -441,7 +441,6 @@ func NewClient() *Client { RetryMax: defaultRetryMax, CheckRetry: DefaultRetryPolicy, Backoff: DefaultBackoff, - PrepareRetry: DefaultPrepareRetry, } } @@ -558,12 +557,6 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response) return sleep } -// DefaultPrepareRetry is performing noop during prepare retry -func DefaultPrepareRetry(_ *http.Request) error { - // noop - return nil -} - // LinearJitterBackoff provides a callback for Client.Backoff which will // perform linear backoff based on the attempt number and with jitter to // prevent a thundering herd. @@ -742,9 +735,11 @@ func (c *Client) Do(req *Request) (*http.Response, error) { httpreq := *req.Request req.Request = &httpreq - if err := c.PrepareRetry(req.Request); err != nil { - prepareErr = err - break + if c.PrepareRetry != nil { + if err := c.PrepareRetry(req.Request); err != nil { + prepareErr = err + break + } } } From 40a3a3381372fec325bd2570e5688d3d7198cd61 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 8 May 2024 21:02:53 +0100 Subject: [PATCH 13/20] Change of maintainer --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index f8389c9..d6dd78a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @hashicorp/release-engineering \ No newline at end of file +* @hashicorp/go-retryablehttp-maintainers From 1661d7b8da5e51f0170fca2b3b9f143ace31d43b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 13:57:24 +0000 Subject: [PATCH 14/20] Bump actions/cache from 3.2.6 to 4.0.2 Bumps [actions/cache](https://github.com/actions/cache) from 3.2.6 to 4.0.2. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/69d9d449aced6a2ede0bc19182fadc3a0a42d2b0...0c45773b623bea8c8e75f6c82b208c3cf94ea4f9) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/go-retryablehttp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-retryablehttp.yml b/.github/workflows/go-retryablehttp.yml index dad25d8..ee0ef06 100644 --- a/.github/workflows/go-retryablehttp.yml +++ b/.github/workflows/go-retryablehttp.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 - run: mkdir -p "$TEST_RESULTS"/go-retryablyhttp - name: restore_cache - uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v3.2.6 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: key: go-mod-v1-{{ checksum "go.sum" }} restore-keys: go-mod-v1-{{ checksum "go.sum" }} From 003233786801a56fccb9d5deb110c400e1decca3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 13:58:30 +0000 Subject: [PATCH 15/20] Bump actions/setup-go from 4.0.0 to 5.0.1 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.0.0 to 5.0.1. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/4d34df0c2316fe8122ab82dc22947d607c0c91f9...cdcb36043654635271a94b9a6d1392de5bb323a7) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/go-retryablehttp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-retryablehttp.yml b/.github/workflows/go-retryablehttp.yml index dad25d8..de02e99 100644 --- a/.github/workflows/go-retryablehttp.yml +++ b/.github/workflows/go-retryablehttp.yml @@ -8,7 +8,7 @@ jobs: TEST_RESULTS: "/tmp/test-results" steps: - name: Setup go - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: 1.18 - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 From 4c2e07b94906d2fed1a53ab4460de157649ce900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 14:10:05 +0000 Subject: [PATCH 16/20] Bump actions/checkout from 3.5.0 to 4.1.5 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8f4b7f84864484a7bf31766abe9204da3cbe65b3...44c2b7a8a4ea60a981eaca3cf939b5f4305c123b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/actionlint.yml | 2 +- .github/workflows/go-retryablehttp.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 00a13c2..e0d2f4f 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -12,7 +12,7 @@ jobs: actionlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: "Check GitHub workflow files" uses: docker://docker.mirror.hashicorp.services/rhysd/actionlint:latest with: diff --git a/.github/workflows/go-retryablehttp.yml b/.github/workflows/go-retryablehttp.yml index dad25d8..1bdf390 100644 --- a/.github/workflows/go-retryablehttp.yml +++ b/.github/workflows/go-retryablehttp.yml @@ -11,7 +11,7 @@ jobs: uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 with: go-version: 1.18 - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - run: mkdir -p "$TEST_RESULTS"/go-retryablyhttp - name: restore_cache uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v3.2.6 From 6d0f2e8894525409ace25b36d67a668e7249d052 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 9 May 2024 16:11:26 +0100 Subject: [PATCH 17/20] Changelog updates for #138, #197, #216 --- CHANGELOG.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a17b9f..e16887d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,22 @@ +## 0.7.6 (Unreleased) + +ENHANCEMENTS: + +- client: support a `RetryPrepare` function for modifying the request before retrying (#216) +- client: support HTTP-date values for `Retry-After` header value (#138) +- client: avoid reading entire body when the body is a `*bytes.Reader` (#197) + ## 0.7.5 (Nov 8, 2023) -BUG FIXES +BUG FIXES: -- client: fixes an issue where the request body is not preserved on temporary redirects or re-established HTTP/2 connections [GH-207] +- client: fixes an issue where the request body is not preserved on temporary redirects or re-established HTTP/2 connections (#207) ## 0.7.4 (Jun 6, 2023) -BUG FIXES +BUG FIXES: -- client: fixing an issue where the Content-Type header wouldn't be sent with an empty payload when using HTTP/2 [GH-194] +- client: fixing an issue where the Content-Type header wouldn't be sent with an empty payload when using HTTP/2 (#194) ## 0.7.3 (May 15, 2023) From 834d13d328ba60d75384836528bf4764a4ec3a06 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 9 May 2024 16:44:54 +0100 Subject: [PATCH 18/20] update go version --- .go-version | 1 + go.mod | 11 +++++++++-- go.sum | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 .go-version diff --git a/.go-version b/.go-version new file mode 100644 index 0000000..6fee2fe --- /dev/null +++ b/.go-version @@ -0,0 +1 @@ +1.22.2 diff --git a/go.mod b/go.mod index d05df1b..12c7872 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,14 @@ module github.com/hashicorp/go-retryablehttp require ( github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hashicorp/go-hclog v0.9.2 + github.com/hashicorp/go-hclog v1.6.3 ) -go 1.13 +require ( + github.com/fatih/color v1.16.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.20.0 // indirect +) + +go 1.19 diff --git a/go.sum b/go.sum index 62d791e..a5da2ce 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,36 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e82c7007a33a9963a7a965e814c6c3944ca09390 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 9 May 2024 16:45:32 +0100 Subject: [PATCH 19/20] GHA workflows for unit tests --- .github/workflows/go-retryablehttp.yml | 46 ----------------------- .github/workflows/pr-gofmt.yaml | 23 ++++++++++++ .github/workflows/pr-unit-tests-1.19.yaml | 17 +++++++++ .github/workflows/pr-unit-tests-1.20.yaml | 17 +++++++++ Makefile | 2 +- 5 files changed, 58 insertions(+), 47 deletions(-) delete mode 100644 .github/workflows/go-retryablehttp.yml create mode 100644 .github/workflows/pr-gofmt.yaml create mode 100644 .github/workflows/pr-unit-tests-1.19.yaml create mode 100644 .github/workflows/pr-unit-tests-1.20.yaml diff --git a/.github/workflows/go-retryablehttp.yml b/.github/workflows/go-retryablehttp.yml deleted file mode 100644 index dd3673e..0000000 --- a/.github/workflows/go-retryablehttp.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Build -on: - push: -jobs: - run-tests: - runs-on: ubuntu-latest - env: - TEST_RESULTS: "/tmp/test-results" - steps: - - name: Setup go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 - with: - go-version: 1.18 - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - - run: mkdir -p "$TEST_RESULTS"/go-retryablyhttp - - name: restore_cache - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - with: - key: go-mod-v1-{{ checksum "go.sum" }} - restore-keys: go-mod-v1-{{ checksum "go.sum" }} - path: "/go/pkg/mod" - - run: go mod download - - run: go mod tidy - - name: Run go format - run: |- - files=$(go fmt ./...) - if [ -n "$files" ]; then - echo "The following file(s) do not conform to go fmt:" - echo "$files" - exit 1 - fi - - name: Install gotestsum - run: go install gotest.tools/gotestsum@latest - - name: Run unit tests - run: |- - PACKAGE_NAMES=$(go list ./...) - # shellcheck disable=SC2086 # can't quote package list - gotestsum --junitfile "${TEST_RESULTS}"/go-retryablyhttp/gotestsum-report.xml -- $PACKAGE_NAMES - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - with: - path: "/tmp/test-results" - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - with: - path: "/tmp/test-results" -permissions: - contents: read diff --git a/.github/workflows/pr-gofmt.yaml b/.github/workflows/pr-gofmt.yaml new file mode 100644 index 0000000..cf5847a --- /dev/null +++ b/.github/workflows/pr-gofmt.yaml @@ -0,0 +1,23 @@ +name: Go format check +on: + pull_request: + types: ['opened', 'synchronize'] + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version-file: ./.go-version + + - name: Run go format + run: |- + files=$(gofmt -s -l .) + if [ -n "$files" ]; then + echo >&2 "The following file(s) are not gofmt compliant:" + echo >&2 "$files" + exit 1 + fi diff --git a/.github/workflows/pr-unit-tests-1.19.yaml b/.github/workflows/pr-unit-tests-1.19.yaml new file mode 100644 index 0000000..e02ee25 --- /dev/null +++ b/.github/workflows/pr-unit-tests-1.19.yaml @@ -0,0 +1,17 @@ +name: Unit tests (Go 1.19) +on: + pull_request: + types: ['opened', 'synchronize'] + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: 1.19 + + - name: Run unit tests + run: make test diff --git a/.github/workflows/pr-unit-tests-1.20.yaml b/.github/workflows/pr-unit-tests-1.20.yaml new file mode 100644 index 0000000..bf492c0 --- /dev/null +++ b/.github/workflows/pr-unit-tests-1.20.yaml @@ -0,0 +1,17 @@ +name: Unit tests (Go 1.20+) +on: + pull_request: + types: ['opened', 'synchronize'] + +jobs: + run-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: 1.22 + + - name: Run unit tests + run: make test diff --git a/Makefile b/Makefile index da17640..5255241 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ default: test test: go vet ./... - go test -race ./... + go test -v -race ./... updatedeps: go get -f -t -u ./... From f67cc6e7850a16c067a86828dbca2a28d21ccc46 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 9 May 2024 16:50:30 +0100 Subject: [PATCH 20/20] deprecations, linting --- client.go | 13 ++++++------- client_test.go | 11 +++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index 9b6a20d..f69063c 100644 --- a/client.go +++ b/client.go @@ -30,7 +30,6 @@ import ( "crypto/x509" "fmt" "io" - "io/ioutil" "log" "math" "math/rand" @@ -264,7 +263,7 @@ func getBodyReaderAndContentLength(rawBody interface{}) (ReaderFunc, int64, erro raw := body bodyReader = func() (io.Reader, error) { _, err := raw.Seek(0, 0) - return ioutil.NopCloser(raw), err + return io.NopCloser(raw), err } if lr, ok := raw.(LenReader); ok { contentLength = int64(lr.Len()) @@ -272,7 +271,7 @@ func getBodyReaderAndContentLength(rawBody interface{}) (ReaderFunc, int64, erro // Read all in so we can reset case io.Reader: - buf, err := ioutil.ReadAll(body) + buf, err := io.ReadAll(body) if err != nil { return nil, 0, err } @@ -619,13 +618,13 @@ func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Resp } // Seed rand; doing this every time is fine - rand := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + source := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) // Pick a random number that lies somewhere between the min and max and // multiply by the attemptNum. attemptNum starts at zero so we always // increment here. We first get a random percentage, then apply that to the // difference between min and max, and add to min. - jitter := rand.Float64() * float64(max-min) + jitter := source.Float64() * float64(max-min) jitterMin := int64(jitter) + int64(min) return time.Duration(jitterMin * int64(attemptNum)) } @@ -675,7 +674,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) { if c, ok := body.(io.ReadCloser); ok { req.Body = c } else { - req.Body = ioutil.NopCloser(body) + req.Body = io.NopCloser(body) } } @@ -820,7 +819,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) { // Try to read the response body so we can reuse this connection. func (c *Client) drainBody(body io.ReadCloser) { defer body.Close() - _, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit)) + _, err := io.Copy(io.Discard, io.LimitReader(body, respReadLimit)) if err != nil { if c.logger() != nil { switch v := c.logger().(type) { diff --git a/client_test.go b/client_test.go index e5c8429..3cba8ec 100644 --- a/client_test.go +++ b/client_test.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "net/http" "net/http/httptest" @@ -206,7 +205,7 @@ func testClientDo(t *testing.T, body interface{}) { } // Check the payload - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("err: %s", err) } @@ -657,7 +656,7 @@ func testClientResponseLogHook(t *testing.T, l interface{}, buf *bytes.Buffer) { } } else { // Log the response body when we get a 500 - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("err: %v", err) } @@ -678,7 +677,7 @@ func testClientResponseLogHook(t *testing.T, l interface{}, buf *bytes.Buffer) { // Make sure we can read the response body still, since we did not // read or close it from the response log hook. - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("err: %v", err) } @@ -997,7 +996,7 @@ func TestClient_Post(t *testing.T) { } // Check the payload - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("err: %s", err) } @@ -1035,7 +1034,7 @@ func TestClient_PostForm(t *testing.T) { } // Check the payload - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("err: %s", err) }