-
Notifications
You must be signed in to change notification settings - Fork 5
/
http_retry.go
128 lines (104 loc) · 2.62 KB
/
http_retry.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
128
// Copyright (c) 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.
package utils
import (
"net/http"
"time"
"github.com/sirupsen/logrus"
)
const (
StartBackoff = 1 * time.Second
MaxBackoff = 5 * time.Second
MaxTries = 1
)
var RetryCodes = map[int]bool{
422: true, // un-processable entity
425: true, // server not ready
500: true, // internal server error
502: true, // bad gateway
408: true, // request timeout error
}
// Backoff interface
type Backoff interface {
// Get delay's duration
Get(attempt uint) time.Duration
}
type exponentialBackoff struct {
// The start duration to delay after the first Next() call
Start time.Duration
// The maximum duration of delay
Max time.Duration
}
func NewExponentialBackoff(start time.Duration, max time.Duration) Backoff {
return &exponentialBackoff{Start: start, Max: max}
}
func (b *exponentialBackoff) Get(attempt uint) time.Duration {
d := b.Start
for i := uint(0); i < attempt; i++ {
d *= time.Duration(2)
if d > b.Max {
return b.Max
}
}
return d
}
type constantBackoff struct {
Duration time.Duration
}
func NewConstantBackoff(duration time.Duration) Backoff {
return &constantBackoff{Duration: duration}
}
func (b *constantBackoff) Get(attempt uint) time.Duration {
return b.Duration
}
// Retry struct with http.RoundTripper.
type Retry struct {
RetryCodes map[int]bool
MaxTries uint
Backoff Backoff
Transport http.RoundTripper
Sleeper func(duration time.Duration)
}
func (m Retry) RoundTrip(req *http.Request) (*http.Response, error) {
var res *http.Response
var err error
sleep := m.Sleeper
if sleep == nil {
sleep = time.Sleep
}
//var body []byte
//if req.Body != nil {
// body, err = ioutil.ReadAll(req.Body)
//}
req, err = SetupRewindBody(req)
if err != nil {
return nil, err
}
for attempt := uint(0); attempt < m.MaxTries+1; attempt++ {
//if req.Body != nil {
// req.Body = ioutil.NopCloser(bytes.NewReader(body)) // Reset body for reading
//}
res, err = m.Transport.RoundTrip(req)
// Do not try again if:
hasError := err != nil
isResponseNil := res == nil
isStatusCodeAllowed := !isResponseNil && !m.RetryCodes[res.StatusCode]
if hasError ||
isResponseNil ||
isStatusCodeAllowed {
return res, err
}
req, err = RewindBody(req)
if err != nil {
return nil, err
}
// Wait (backoff) and try again (retry)
if attempt != m.MaxTries-1 {
backOffDuration := m.Backoff.Get(attempt)
sleep(backOffDuration)
}
logrus.Infof("Retrying attempt: %v", attempt)
}
return res, err
}