-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathretries_http.go
127 lines (106 loc) · 3.66 KB
/
retries_http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package linodego
import (
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"time"
"golang.org/x/net/http2"
)
const (
// nolint:unused
httpRetryAfterHeaderName = "Retry-After"
// nolint:unused
httpMaintenanceModeHeaderName = "X-Maintenance-Mode"
// nolint:unused
httpDefaultRetryCount = 1000
)
// RetryConditional is a type alias for a function that determines if a request should be retried based on the response and error.
// nolint:unused
type httpRetryConditional func(*http.Response, error) bool
// RetryAfter is a type alias for a function that determines the duration to wait before retrying based on the response.
// nolint:unused
type httpRetryAfter func(*http.Response) (time.Duration, error)
// Configures http.Client to lock until enough time has passed to retry the request as determined by the Retry-After response header.
// If the Retry-After header is not set, we fall back to the value of SetPollDelay.
// nolint:unused
func httpConfigureRetries(c *httpClient) {
c.retryConditionals = append(c.retryConditionals, httpcheckRetryConditionals(c))
c.retryAfter = httpRespectRetryAfter
}
// nolint:unused
func httpcheckRetryConditionals(c *httpClient) httpRetryConditional {
return func(resp *http.Response, err error) bool {
for _, retryConditional := range c.retryConditionals {
retry := retryConditional(resp, err)
if retry {
log.Printf("[INFO] Received error %v - Retrying", err)
return true
}
}
return false
}
}
// nolint:unused
func httpRespectRetryAfter(resp *http.Response) (time.Duration, error) {
retryAfterStr := resp.Header.Get(retryAfterHeaderName)
if retryAfterStr == "" {
return 0, nil
}
retryAfter, err := strconv.Atoi(retryAfterStr)
if err != nil {
return 0, err
}
duration := time.Duration(retryAfter) * time.Second
log.Printf("[INFO] Respecting Retry-After Header of %d (%s)", retryAfter, duration)
return duration, nil
}
// Retry conditions
// nolint:unused
func httpLinodeBusyRetryCondition(resp *http.Response, _ error) bool {
apiError, ok := getAPIError(resp)
linodeBusy := ok && apiError.Error() == "Linode busy."
retry := resp.StatusCode == http.StatusBadRequest && linodeBusy
return retry
}
// nolint:unused
func httpTooManyRequestsRetryCondition(resp *http.Response, _ error) bool {
return resp.StatusCode == http.StatusTooManyRequests
}
// nolint:unused
func httpServiceUnavailableRetryCondition(resp *http.Response, _ error) bool {
serviceUnavailable := resp.StatusCode == http.StatusServiceUnavailable
// During maintenance events, the API will return a 503 and add
// an `X-MAINTENANCE-MODE` header. Don't retry during maintenance
// events, only for legitimate 503s.
if serviceUnavailable && resp.Header.Get(maintenanceModeHeaderName) != "" {
log.Printf("[INFO] Linode API is under maintenance, request will not be retried - please see status.linode.com for more information")
return false
}
return serviceUnavailable
}
// nolint:unused
func httpRequestTimeoutRetryCondition(resp *http.Response, _ error) bool {
return resp.StatusCode == http.StatusRequestTimeout
}
// nolint:unused
func httpRequestGOAWAYRetryCondition(_ *http.Response, err error) bool {
return errors.As(err, &http2.GoAwayError{})
}
// nolint:unused
func httpRequestNGINXRetryCondition(resp *http.Response, _ error) bool {
return resp.StatusCode == http.StatusBadRequest &&
resp.Header.Get("Server") == "nginx" &&
resp.Header.Get("Content-Type") == "text/html"
}
// Helper function to extract APIError from response
// nolint:unused
func getAPIError(resp *http.Response) (*APIError, bool) {
var apiError APIError
err := json.NewDecoder(resp.Body).Decode(&apiError)
if err != nil {
return nil, false
}
return &apiError, true
}