summaryrefslogtreecommitdiff
path: root/internal/transport/transport.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/transport/transport.go')
-rw-r--r--internal/transport/transport.go70
1 files changed, 44 insertions, 26 deletions
diff --git a/internal/transport/transport.go b/internal/transport/transport.go
index 4c1e890ef..8095e6612 100644
--- a/internal/transport/transport.go
+++ b/internal/transport/transport.go
@@ -27,6 +27,7 @@ import (
"io"
"net/http"
"net/url"
+ "strconv"
"strings"
"sync"
"time"
@@ -84,36 +85,37 @@ type transport struct {
}
// GET will perform given http request using transport client, retrying on certain preset errors, or if status code is among retryOn.
-func (t *transport) GET(r *http.Request, retryOn ...int) (*http.Response, error) {
+func (t *transport) GET(r *http.Request) (*http.Response, error) {
if r.Method != http.MethodGet {
return nil, errors.New("must be GET request")
}
return t.do(r, func(r *http.Request) error {
return t.signGET(r)
- }, retryOn...)
+ })
}
// POST will perform given http request using transport client, retrying on certain preset errors, or if status code is among retryOn.
-func (t *transport) POST(r *http.Request, body []byte, retryOn ...int) (*http.Response, error) {
+func (t *transport) POST(r *http.Request, body []byte) (*http.Response, error) {
if r.Method != http.MethodPost {
return nil, errors.New("must be POST request")
}
return t.do(r, func(r *http.Request) error {
return t.signPOST(r, body)
- }, retryOn...)
+ })
}
-func (t *transport) do(r *http.Request, signer func(*http.Request) error, retryOn ...int) (*http.Response, error) {
- const maxRetries = 5
+func (t *transport) do(r *http.Request, signer func(*http.Request) error) (*http.Response, error) {
+ const (
+ // max no. attempts
+ maxRetries = 5
- var (
- // Initial backoff duration
- backoff = 2 * time.Second
-
- // Get request hostname
- host = r.URL.Hostname()
+ // starting backoff duration.
+ baseBackoff = 2 * time.Second
)
+ // Get request hostname
+ host := r.URL.Hostname()
+
// Check if recently reached max retries for this host
// so we don't need to bother reattempting it. The only
// errors that are retried upon are server failure and
@@ -137,6 +139,8 @@ func (t *transport) do(r *http.Request, signer func(*http.Request) error, retryO
r.Header.Set("User-Agent", t.controller.userAgent)
for i := 0; i < maxRetries; i++ {
+ var backoff time.Duration
+
// Reset signing header fields
now := t.controller.clock.Now().UTC()
r.Header.Set("Date", now.Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
@@ -152,18 +156,35 @@ func (t *transport) do(r *http.Request, signer func(*http.Request) error, retryO
// Attempt to perform request
rsp, err := t.controller.client.Do(r)
- if err == nil { //nolint shutup linter
+ if err == nil { //nolint:gocritic
// TooManyRequest means we need to slow
// down and retry our request. Codes over
// 500 generally indicate temp. outages.
if code := rsp.StatusCode; code < 500 &&
- code != http.StatusTooManyRequests &&
- !containsInt(retryOn, rsp.StatusCode) {
+ code != http.StatusTooManyRequests {
return rsp, nil
}
// Generate error from status code for logging
err = errors.New(`http response "` + rsp.Status + `"`)
+
+ // Search for a provided "Retry-After" header value.
+ if after := rsp.Header.Get("Retry-After"); after != "" {
+
+ if u, _ := strconv.ParseUint(after, 10, 32); u != 0 {
+ // An integer number of backoff seconds was provided.
+ backoff = time.Duration(u) * time.Second
+ } else if at, _ := http.ParseTime(after); !at.Before(now) {
+ // An HTTP formatted future date-time was provided.
+ backoff = at.Sub(now)
+ }
+
+ // Don't let their provided backoff exceed our max.
+ if max := baseBackoff * maxRetries; backoff > max {
+ backoff = max
+ }
+ }
+
} else if errorsv2.Is(err,
context.DeadlineExceeded,
context.Canceled,
@@ -179,11 +200,18 @@ func (t *transport) do(r *http.Request, signer func(*http.Request) error, retryO
} else if errors.As(err, &x509.UnknownAuthorityError{}) {
// Unknown authority errors we do NOT recover from
return nil, err
- } else if fastFail {
+ }
+
+ if fastFail {
// on fast-fail, don't bother backoff/retry
return nil, fmt.Errorf("%w (fast fail)", err)
}
+ if backoff == 0 {
+ // No retry-after found, set our predefined backoff.
+ backoff = time.Duration(i) * baseBackoff
+ }
+
l.Errorf("backing off for %s after http request error: %v", backoff.String(), err)
select {
@@ -238,13 +266,3 @@ func (t *transport) safesign(sign func()) {
// Perform signing
sign()
}
-
-// containsInt checks if slice contains check.
-func containsInt(slice []int, check int) bool {
- for _, i := range slice {
- if i == check {
- return true
- }
- }
- return false
-}