-
Notifications
You must be signed in to change notification settings - Fork 12
/
backoff.transport.go
95 lines (85 loc) · 2.43 KB
/
backoff.transport.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
// Copyright 2022 The searKing Author. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http
import (
"fmt"
"net/http"
"time"
time_ "github.com/searKing/golang/go/time"
)
// RoundTripperWithBackoff wraps http.RoundTripper retryable by backoff.
func RoundTripperWithBackoff(rt http.RoundTripper, opts ...DoWithBackoffOption) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (resp *http.Response, err error) {
var opt doWithBackoff
opt.SetDefault()
if rt == nil {
rt = http.DefaultTransport
}
opt.DoRetryHandler = func(req *http.Request, retry int) (*http.Response, error) { return rt.RoundTrip(req) }
opt.ApplyOptions(opts...)
if opt.RetryAfter == nil {
opt.RetryAfter = RetryAfter
}
opt.Complete()
var option []time_.ExponentialBackOffOption
option = append(option, time_.WithExponentialBackOffOptionMaxElapsedCount(3))
option = append(option, opt.ExponentialBackOffOption...)
backoff := time_.NewDefaultExponentialBackOff(option...)
rewindableErr := RequestWithBodyRewindable(req)
var retries int
for {
if retries > 0 && req.GetBody != nil {
newBody, err := req.GetBody()
if err != nil {
return nil, err
}
req.Body = newBody
}
var do = opt.DoRetryHandler
httpDo := do
if opt.clientInterceptor != nil {
httpDo = func(req *http.Request, retry int) (*http.Response, error) {
return opt.clientInterceptor(req, retry, do, opts...)
}
}
resp, err := httpDo(req, retries)
wait, ok := backoff.NextBackOff()
if !ok {
if err != nil {
return nil, fmt.Errorf("http do reach backoff limit after retries %d: %w", retries, err)
} else {
return resp, nil
}
}
wait, retry := opt.RetryAfter(resp, err, wait)
if !retry {
if err != nil {
return nil, fmt.Errorf("http do reach server limit after retries %d: %w", retries, err)
} else {
return resp, nil
}
}
if rewindableErr != nil {
if err != nil {
return nil, fmt.Errorf("http do cannot rewindbody after retries %d: %w", retries, err)
} else {
return resp, nil
}
}
timer := time.NewTimer(wait)
select {
case <-timer.C:
retries++
continue
case <-req.Context().Done():
timer.Stop()
if err != nil {
return nil, fmt.Errorf("http do canceled after retries %d: %w", retries, err)
} else {
return resp, nil
}
}
}
})
}