diff --git a/vendor/github.com/hashicorp/go-retryablehttp/client.go b/vendor/github.com/hashicorp/go-retryablehttp/client.go index cec59b853..198779bdf 100644 --- a/vendor/github.com/hashicorp/go-retryablehttp/client.go +++ b/vendor/github.com/hashicorp/go-retryablehttp/client.go @@ -32,12 +32,16 @@ import ( var ( // Default retry configuration defaultRetryWaitMin = 1 * time.Second - defaultRetryWaitMax = 5 * time.Minute - defaultRetryMax = 32 + defaultRetryWaitMax = 30 * time.Second + defaultRetryMax = 4 // defaultClient is used for performing requests without explicitly making // a new client. It is purposely private to avoid modifications. defaultClient = NewClient() + + // We need to consume response bodies to maintain http connections, but + // limit the size we consume to respReadLimit. + respReadLimit = int64(4096) ) // LenReader is an interface implemented by many in-memory io.Reader's. Used @@ -93,6 +97,16 @@ type RequestLogHook func(*log.Logger, *http.Request, int) // from this method, this will affect the response returned from Do(). type ResponseLogHook func(*log.Logger, *http.Response) +// CheckRetry specifies a policy for handling retries. It is called +// following each request with the response and error values returned by +// the http.Client. If CheckRetry returns false, the Client stops retrying +// and returns the response to the caller. If CheckRetry returns an error, +// that error value is returned in lieu of the error from the request. The +// Client will close any response body when retrying, but if the retry is +// aborted it is up to the CheckResponse callback to properly close any +// response body before returning. +type CheckRetry func(resp *http.Response, err error) (bool, error) + // Client is used to make HTTP requests. It adds additional functionality // like automatic retries to tolerate minor outages. type Client struct { @@ -110,6 +124,10 @@ type Client struct { // ResponseLogHook allows a user-supplied function to be called // with the response from each HTTP request executed. ResponseLogHook ResponseLogHook + + // CheckRetry specifies the policy for handling retries, and is called + // after each request. The default policy is DefaultRetryPolicy. + CheckRetry CheckRetry } // NewClient creates a new Client with default settings. @@ -120,9 +138,27 @@ func NewClient() *Client { RetryWaitMin: defaultRetryWaitMin, RetryWaitMax: defaultRetryWaitMax, RetryMax: defaultRetryMax, + CheckRetry: DefaultRetryPolicy, } } +// DefaultRetryPolicy provides a default callback for Client.CheckRetry, which +// will retry on connection errors and server errors. +func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) { + if err != nil { + return true, err + } + // Check the response code. We retry on 500-range responses to allow + // the server time to recover, as 500's are typically not permanent + // errors and may relate to outages on the server side. This will catch + // invalid response codes as well, like 0 and 999. + if resp.StatusCode == 0 || resp.StatusCode >= 500 { + return true, nil + } + + return false, nil +} + // 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) @@ -143,27 +179,34 @@ func (c *Client) Do(req *Request) (*http.Response, error) { // Attempt the request resp, err := c.HTTPClient.Do(req.Request) + + // 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) - goto RETRY - } - code = resp.StatusCode - - // Call the response logger function if provided. - if c.ResponseLogHook != nil { - c.ResponseLogHook(c.Logger, resp) + } else { + // Call this here to maintain the behavior of logging all requests, + // even if CheckRetry signals to stop. + if c.ResponseLogHook != nil { + // Call the response logger function if provided. + c.ResponseLogHook(c.Logger, resp) + } } - // Check the response code. We retry on 500-range responses to allow - // the server time to recover, as 500's are typically not permanent - // errors and may relate to outages on the server side. - if code%500 < 100 { - resp.Body.Close() - goto RETRY + // Now decide if we should continue. + if !checkOK { + if checkErr != nil { + err = checkErr + } + return resp, err + } + + // We're going to retry, consume any response to reuse the connection. + if err == nil { + c.drainBody(resp.Body) } - return resp, nil - RETRY: remain := c.RetryMax - i if remain == 0 { break @@ -182,6 +225,15 @@ func (c *Client) Do(req *Request) (*http.Response, error) { req.Method, req.URL, c.RetryMax+1) } +// 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)) + if err != nil { + c.Logger.Printf("[ERR] error reading response body: %v", err) + } +} + // Get is a shortcut for doing a GET request without making a new client. func Get(url string) (*http.Response, error) { return defaultClient.Get(url) diff --git a/vendor/vendor.json b/vendor/vendor.json index a79b1592f..fb2907542 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -626,10 +626,10 @@ "revisionTime": "2016-06-08T02:21:58Z" }, { - "checksumSHA1": "bfVGm7xZ2VFpddJp3KEPUZ8Y9Po=", + "checksumSHA1": "ErJHGU6AVPZM9yoY/xV11TwSjQs=", "path": "github.com/hashicorp/go-retryablehttp", - "revision": "886ce0458bc81ccca0fb7044c1be0e9ab590bed7", - "revisionTime": "2016-07-18T23:34:41Z" + "revision": "6e85be8fee1dcaa02c0eaaac2df5a8fbecf94145", + "revisionTime": "2016-09-30T03:51:02Z" }, { "checksumSHA1": "A1PcINvF3UiwHRKn8UcgARgvGRs=",