Update go-retryablehttp vendor

This commit is contained in:
Jeff Mitchell 2018-05-09 17:44:53 -04:00
parent ec4b839741
commit 7f886b5675
5 changed files with 146 additions and 26 deletions

View file

@ -16,9 +16,9 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-cleanhttp"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/sethgrid/pester"
"golang.org/x/net/http2"
)
@ -69,6 +69,9 @@ type Config struct {
// If there is an error when creating the configuration, this will be the
// error
Error error
// The Backoff function to use; a default is used if not provided
Backoff retryablehttp.Backoff
}
// TLSConfig contains the parameters needed to configure TLS on the HTTP client
@ -131,12 +134,14 @@ func DefaultConfig() *Config {
// but in e.g. http_test actual redirect handling is necessary
config.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// Returning this value causes the Go net library to not close the
// response body and to nil out the error. Otherwise pester tries
// three times on every redirect because it sees an error from this
// response body and to nil out the error. Otherwise retry clients may
// try three times on every redirect because it sees an error from this
// function (to prevent redirects) passing through to it.
return http.ErrUseLastResponse
}
config.Backoff = retryablehttp.LinearJitterBackoff
return config
}
@ -434,6 +439,16 @@ func (c *Client) SetHeaders(headers http.Header) {
c.headers = headers
}
// SetBackoff sets the backoff function to be used for future requests.
func (c *Client) SetBackoff(backoff retryablehttp.Backoff) {
c.modifyLock.RLock()
c.config.modifyLock.Lock()
defer c.config.modifyLock.Unlock()
c.modifyLock.RUnlock()
c.config.Backoff = backoff
}
// Clone creates a new client with the same configuration. Note that the same
// underlying http.Client is used; modifying the client from more than one
// goroutine at once may not be safe, so modify the client as needed and then
@ -449,6 +464,7 @@ func (c *Client) Clone() (*Client, error) {
HttpClient: config.HttpClient,
MaxRetries: config.MaxRetries,
Timeout: config.Timeout,
Backoff: config.Backoff,
}
config.modifyLock.RUnlock()
@ -544,14 +560,35 @@ func (c *Client) RawRequest(r *Request) (*Response, error) {
redirectCount := 0
START:
req, err := r.ToHTTP()
req, err := r.toRetryableHTTP(false)
if err != nil {
return nil, err
}
if req == nil {
return nil, fmt.Errorf("nil request created")
}
client := pester.NewExtendedClient(c.config.HttpClient)
client.Backoff = pester.LinearJitterBackoff
client.MaxRetries = c.config.MaxRetries
backoff := c.config.Backoff
if backoff == nil {
backoff = retryablehttp.LinearJitterBackoff
}
maxRetries := c.config.MaxRetries
if maxRetries == 0 {
maxRetries = 2
}
client := &retryablehttp.Client{
HTTPClient: c.config.HttpClient,
RetryWaitMin: 1 * time.Second,
RetryWaitMax: 5 * time.Second,
RetryMax: maxRetries,
CheckRetry: retryablehttp.DefaultRetryPolicy,
Backoff: backoff,
ErrorHandler: func(resp *http.Response, err error, numTries int) (*http.Response, error) {
return resp, err
},
}
var result *Response
resp, err := client.Do(req)

View file

@ -4,8 +4,11 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
retryablehttp "github.com/hashicorp/go-retryablehttp"
)
// Request is a raw request configuration structure used to initiate
@ -53,13 +56,41 @@ func (r *Request) ResetJSONBody() error {
// ToHTTP turns this request into a valid *http.Request for use with the
// net/http package.
func (r *Request) ToHTTP() (*http.Request, error) {
req, err := r.toRetryableHTTP(true)
if err != nil {
return nil, err
}
return req.Request, nil
}
func (r *Request) toRetryableHTTP(regular bool) (*retryablehttp.Request, error) {
// Encode the query parameters
r.URL.RawQuery = r.Params.Encode()
// Create the HTTP request
req, err := http.NewRequest(r.Method, r.URL.RequestURI(), r.Body)
if err != nil {
return nil, err
// Create the HTTP request, defaulting to retryable
var req *retryablehttp.Request
if regular {
regReq, err := http.NewRequest(r.Method, r.URL.RequestURI(), r.Body)
if err != nil {
return nil, err
}
req = &retryablehttp.Request{
Request: regReq,
}
} else {
var buf []byte
var err error
if r.Body != nil {
buf, err = ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
}
req, err = retryablehttp.NewRequest(r.Method, r.URL.RequestURI(), bytes.NewReader(buf))
if err != nil {
return nil, err
}
}
req.URL.User = r.URL.User

View file

@ -572,7 +572,7 @@ ssl_storage_port: 7001
#
# Setting listen_address to 0.0.0.0 is always wrong.
#
listen_address: 172.17.0.5
listen_address: 172.17.0.4
# Set listen_address OR listen_interface, not both. Interfaces must correspond
# to a single address, IP aliasing is not supported.

View file

@ -20,6 +20,7 @@ import (
"io/ioutil"
"log"
"math"
"math/rand"
"net/http"
"net/url"
"os"
@ -112,6 +113,12 @@ type CheckRetry func(resp *http.Response, err error) (bool, error)
// that should pass before trying again.
type Backoff func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration
// ErrorHandler is called if retries are expired, containing the last status
// from the http library. The default will close the body and return an error
// indicating how many tries were 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)
// Client is used to make HTTP requests. It adds additional functionality
// like automatic retries to tolerate minor outages.
type Client struct {
@ -136,6 +143,9 @@ type Client struct {
// Backoff specifies the policy for how long to wait between retries
Backoff Backoff
// ErrorHandler specifies the custom error handler to use, if any
ErrorHandler ErrorHandler
}
// NewClient creates a new Client with default settings.
@ -180,9 +190,31 @@ func DefaultBackoff(min, max time.Duration, attemptNum int, resp *http.Response)
return sleep
}
// LinearJitterBackoff provides a callback for Client.Backoff which will
// perform linear backoff based on the attempt number and limited by the
// provided minimum and maximum, which are applied *prior to linear
// adjustment*. Jitter will be applied to prevent a thundering herd.
func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
if max <= min {
// Unclear what to do here, so return min
return min
}
// Seed rand; doing this every time is fine
rand := 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.
return time.Duration((int64(rand.Int63()) % int64(max-min)) * int64((attemptNum + 1)))
}
// Do wraps calling an HTTP method with retries.
func (c *Client) Do(req *Request) (*http.Response, error) {
c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
if c.Logger != nil {
c.Logger.Printf("[DEBUG] %s %s", req.Method, req.URL)
}
var resp *http.Response
var err error
for i := 0; ; i++ {
var code int // HTTP response code
@ -199,13 +231,18 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
}
// Attempt the request
resp, err := c.HTTPClient.Do(req.Request)
resp, err = c.HTTPClient.Do(req.Request)
if resp != nil {
code = resp.StatusCode
}
// Check if we should continue with retries.
checkOK, checkErr := c.CheckRetry(resp, err)
if err != nil {
c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
if c.Logger != nil {
c.Logger.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err)
}
} else {
// Call this here to maintain the behavior of logging all requests,
// even if CheckRetry signals to stop.
@ -223,25 +260,38 @@ func (c *Client) Do(req *Request) (*http.Response, error) {
return resp, err
}
// We're going to retry, consume any response to reuse the connection.
if err == nil {
c.drainBody(resp.Body)
}
// We do this before drainBody beause there's no need for the I/O if
// we're breaking out
remain := c.RetryMax - i
if remain == 0 {
break
}
// We're going to retry, consume any response to reuse the connection.
if err == nil && resp != nil {
c.drainBody(resp.Body)
}
wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp)
desc := fmt.Sprintf("%s %s", req.Method, req.URL)
if code > 0 {
desc = fmt.Sprintf("%s (status: %d)", desc, code)
}
c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
if c.Logger != nil {
c.Logger.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain)
}
time.Sleep(wait)
}
// Return an error if we fall out of the retry loop
if c.ErrorHandler != nil {
return c.ErrorHandler(resp, err, c.RetryMax+1)
}
// By default, we close the response body and return an error without
// returning the response
if resp != nil {
resp.Body.Close()
}
return nil, fmt.Errorf("%s %s giving up after %d attempts",
req.Method, req.URL, c.RetryMax+1)
}
@ -251,7 +301,9 @@ func (c *Client) drainBody(body io.ReadCloser) {
defer body.Close()
_, err := io.Copy(ioutil.Discard, io.LimitReader(body, respReadLimit))
if err != nil {
c.Logger.Printf("[ERR] error reading response body: %v", err)
if c.Logger != nil {
c.Logger.Printf("[ERR] error reading response body: %v", err)
}
}
}

6
vendor/vendor.json vendored
View file

@ -1141,10 +1141,10 @@
"revisionTime": "2018-03-31T00:25:53Z"
},
{
"checksumSHA1": "yzoWV7yrS/TvOrKy5ZrdUjsYaOA=",
"checksumSHA1": "dWD+1wzyeQJnmdfFORXX7vU3xs4=",
"path": "github.com/hashicorp/go-retryablehttp",
"revision": "794af36148bf63c118d6db80eb902a136b907e71",
"revisionTime": "2017-08-24T18:08:59Z"
"revision": "ba24e6e2765455196ea08f97ca8b2a5587d704ec",
"revisionTime": "2018-05-09T21:39:37Z"
},
{
"checksumSHA1": "A1PcINvF3UiwHRKn8UcgARgvGRs=",