rebase: bump github.com/hashicorp/go-retryablehttp from 0.7.1 to 0.7.7

Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.1 to 0.7.7.
- [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.1...v0.7.7)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-retryablehttp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 2131a84a53)
This commit is contained in:
dependabot[bot]
2024-06-24 22:09:21 +00:00
committed by Praveen M
parent 46e4e3fa82
commit 1bef29247a
16 changed files with 236 additions and 86 deletions

View File

@ -0,0 +1 @@
1.22.2

View File

@ -0,0 +1,33 @@
## 0.7.7 (May 30, 2024)
BUG FIXES:
- client: avoid potentially leaking URL-embedded basic authentication credentials in logs (#158)
## 0.7.6 (May 9, 2024)
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)
BUG FIXES:
- client: fix a broken check for invalid server certificate in go 1.20+ (#210)
## 0.7.5 (Nov 8, 2023)
BUG FIXES:
- 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:
- 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)
Initial release

View File

@ -0,0 +1 @@
* @hashicorp/go-retryablehttp-maintainers

View File

@ -1,3 +1,5 @@
Copyright (c) 2015 HashiCorp, Inc.
Mozilla Public License, version 2.0
1. Definitions

View File

@ -2,7 +2,7 @@ default: test
test:
go vet ./...
go test -race ./...
go test -v -race ./...
updatedeps:
go get -f -t -u ./...

View File

@ -45,25 +45,6 @@ The returned response object is an `*http.Response`, the same thing you would
usually get from `net/http`. Had the request failed one or more times, the above
call would block and retry with exponential backoff.
## Retrying cases that fail after a seeming success
It's possible for a request to succeed in the sense that the expected response headers are received, but then to encounter network-level errors while reading the response body. In go-retryablehttp's most basic usage, this error would not be retryable, due to the out-of-band handling of the response body. In some cases it may be desirable to handle the response body as part of the retryable operation.
A toy example (which will retry the full request and succeed on the second attempt) is shown below:
```go
c := retryablehttp.NewClient()
r := retryablehttp.NewRequest("GET", "://foo", nil)
handlerShouldRetry := true
r.SetResponseHandler(func(*http.Response) error {
if !handlerShouldRetry {
return nil
}
handlerShouldRetry = false
return errors.New("retryable error")
})
```
## Getting a stdlib `*http.Client` with retries
It's possible to convert a `*retryablehttp.Client` directly to a `*http.Client`.
@ -78,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).

View File

@ -0,0 +1,14 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build !go1.20
// +build !go1.20
package retryablehttp
import "crypto/x509"
func isCertError(err error) bool {
_, ok := err.(x509.UnknownAuthorityError)
return ok
}

View File

@ -0,0 +1,14 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build go1.20
// +build go1.20
package retryablehttp
import "crypto/tls"
func isCertError(err error) bool {
_, ok := err.(*tls.CertificateVerificationError)
return ok
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package retryablehttp provides a familiar HTTP client interface with
// automatic retries and exponential backoff. It is a thin wrapper over the
// standard net/http client library and exposes nearly the same public API.
@ -24,10 +27,8 @@ package retryablehttp
import (
"bytes"
"context"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"math/rand"
@ -60,6 +61,10 @@ var (
// limit the size we consume to respReadLimit.
respReadLimit = int64(4096)
// 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
// A regular expression to match the error returned by net/http when the
// configured number of redirects is exhausted. This error isn't typed
// specifically so we resort to matching on the error string.
@ -70,6 +75,11 @@ var (
// specifically so we resort to matching on the error string.
schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`)
// A regular expression to match the error returned by net/http when a
// request header or value is invalid. This error isn't typed
// specifically so we resort to matching on the error string.
invalidHeaderErrorRe = regexp.MustCompile(`invalid header`)
// A regular expression to match the error returned by net/http when the
// TLS certificate is not trusted. This error isn't typed
// specifically so we resort to matching on the error string.
@ -80,8 +90,15 @@ var (
type ReaderFunc func() (io.Reader, error)
// ResponseHandlerFunc is a type of function that takes in a Response, and does something with it.
// It only runs if the initial part of the request was successful.
// If an error is returned, the client's retry policy will be used to determine whether to retry the whole request.
// The ResponseHandlerFunc is called when the HTTP client successfully receives a response and the
// CheckRetry function indicates that a retry of the base request is not necessary.
// If an error is returned from this function, the CheckRetry policy will be used to determine
// whether to retry the whole request (including this handler).
//
// Make sure to check status codes! Even if the request was completed it may have a non-2xx status code.
//
// The response body is not automatically closed. It must be closed either by the ResponseHandlerFunc or
// by the caller out-of-band. Failure to do so will result in a memory leak.
type ResponseHandlerFunc func(*http.Response) error
// LenReader is an interface implemented by many in-memory io.Reader's. Used
@ -150,6 +167,20 @@ func (r *Request) SetBody(rawBody interface{}) error {
}
r.body = bodyReader
r.ContentLength = contentLength
if bodyReader != nil {
r.GetBody = func() (io.ReadCloser, error) {
body, err := bodyReader()
if err != nil {
return nil, err
}
if rc, ok := body.(io.ReadCloser); ok {
return rc, nil
}
return io.NopCloser(body), nil
}
} else {
r.GetBody = func() (io.ReadCloser, error) { return http.NoBody, nil }
}
return nil
}
@ -224,21 +255,19 @@ 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:
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())
@ -246,14 +275,21 @@ 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
}
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf), nil
if len(buf) == 0 {
bodyReader = func() (io.Reader, error) {
return http.NoBody, nil
}
contentLength = 0
} else {
bodyReader = func() (io.Reader, error) {
return bytes.NewReader(buf), nil
}
contentLength = int64(len(buf))
}
contentLength = int64(len(buf))
// No body provided, nothing to do
case nil:
@ -285,18 +321,19 @@ func NewRequest(method, url string, rawBody interface{}) (*Request, error) {
// The context controls the entire lifetime of a request and its response:
// obtaining a connection, sending the request, and reading the response headers and body.
func NewRequestWithContext(ctx context.Context, method, url string, rawBody interface{}) (*Request, error) {
bodyReader, contentLength, err := getBodyReaderAndContentLength(rawBody)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, err
}
httpReq.ContentLength = contentLength
return &Request{body: bodyReader, Request: httpReq}, nil
req := &Request{
Request: httpReq,
}
if err := req.SetBody(rawBody); err != nil {
return nil, err
}
return req, nil
}
// Logger interface allows to use other loggers than
@ -361,6 +398,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)
// 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.
type Client struct {
@ -389,6 +429,9 @@ type Client struct {
// ErrorHandler specifies the custom error handler to use, if any
ErrorHandler ErrorHandler
// PrepareRetry can prepare the request for retry operation, for example re-sign it
PrepareRetry PrepareRetry
loggerInit sync.Once
clientInit sync.Once
}
@ -462,11 +505,16 @@ func baseRetryPolicy(resp *http.Response, err error) (bool, error) {
return false, v
}
// Don't retry if the error was due to an invalid header.
if invalidHeaderErrorRe.MatchString(v.Error()) {
return false, v
}
// Don't retry if the error was due to TLS cert verification failure.
if notTrustedErrorRe.MatchString(v.Error()) {
return false, v
}
if _, ok := v.Err.(x509.UnknownAuthorityError); ok {
if isCertError(v.Err) {
return false, v
}
}
@ -503,10 +551,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
}
}
}
@ -519,6 +565,41 @@ 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
// 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
//
// 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 sleep, err := strconv.ParseInt(header, 10, 64); err == nil {
if sleep < 0 { // a negative sleep doesn't make sense
return 0, false
}
return time.Second * time.Duration(sleep), true
}
// Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
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.
@ -546,13 +627,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))
}
@ -577,19 +658,19 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
if logger != nil {
switch v := logger.(type) {
case LeveledLogger:
v.Debug("performing request", "method", req.Method, "url", req.URL)
v.Debug("performing request", "method", req.Method, "url", redactURL(req.URL))
case Logger:
v.Printf("[DEBUG] %s %s", req.Method, req.URL)
v.Printf("[DEBUG] %s %s", req.Method, redactURL(req.URL))
}
}
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.
@ -602,7 +683,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)
}
}
@ -634,9 +715,9 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
if err != nil {
switch v := logger.(type) {
case LeveledLogger:
v.Error("request failed", "error", err, "method", req.Method, "url", req.URL)
v.Error("request failed", "error", err, "method", req.Method, "url", redactURL(req.URL))
case Logger:
v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
v.Printf("[ERR] %s %s request failed: %v", req.Method, redactURL(req.URL), err)
}
} else {
// Call this here to maintain the behavior of logging all requests,
@ -672,7 +753,7 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
if logger != nil {
desc := fmt.Sprintf("%s %s", req.Method, req.URL)
desc := fmt.Sprintf("%s %s", req.Method, redactURL(req.URL))
if resp != nil {
desc = fmt.Sprintf("%s (status: %d)", desc, resp.StatusCode)
}
@ -696,17 +777,26 @@ 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 c.PrepareRetry != nil {
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
@ -728,17 +818,17 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
// communicate why
if err == nil {
return nil, fmt.Errorf("%s %s giving up after %d attempt(s)",
req.Method, req.URL, attempt)
req.Method, redactURL(req.URL), attempt)
}
return nil, fmt.Errorf("%s %s giving up after %d attempt(s): %w",
req.Method, req.URL, attempt, err)
req.Method, redactURL(req.URL), attempt, err)
}
// 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) {
@ -813,3 +903,17 @@ func (c *Client) StandardClient() *http.Client {
Transport: &RoundTripper{Client: c},
}
}
// Taken from url.URL#Redacted() which was introduced in go 1.15.
// We can switch to using it directly if we'll bump the minimum required go version.
func redactURL(u *url.URL) string {
if u == nil {
return ""
}
ru := *u
if _, has := ru.User.Password(); has {
ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
}
return ru.String()
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package retryablehttp
import (

View File

@ -1,6 +1,7 @@
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
// +build darwin freebsd openbsd netbsd dragonfly hurd
// +build !appengine
// +build !tinygo
package isatty

View File

@ -1,5 +1,6 @@
//go:build appengine || js || nacl || wasm
// +build appengine js nacl wasm
//go:build (appengine || js || nacl || tinygo || wasm) && !windows
// +build appengine js nacl tinygo wasm
// +build !windows
package isatty

View File

@ -1,6 +1,7 @@
//go:build (linux || aix || zos) && !appengine
//go:build (linux || aix || zos) && !appengine && !tinygo
// +build linux aix zos
// +build !appengine
// +build !tinygo
package isatty