-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathretrywrapper.go
155 lines (141 loc) · 4.74 KB
/
retrywrapper.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package clients
import (
"bytes"
"code.cloudfoundry.org/cli/api/cloudcontroller"
"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
"code.cloudfoundry.org/cli/api/router"
"errors"
"io"
"net/http"
"reflect"
)
// RetryRequest is a wrapper that retries failed requests if they contain a 5XX
// status code.
// copy of wrapper retry request in cli but remove the necessary
// of have a readseeker body (annoying for sending in fullstream)
type RetryRequest struct {
maxRetries int
connection cloudcontroller.Connection
}
// NewRetryRequest returns a pointer to a RetryRequest wrapper.
func NewRetryRequest(maxRetries int) *RetryRequest {
return &RetryRequest{
maxRetries: maxRetries,
}
}
// Make retries the request if it comes back with a 5XX status code.
func (retry *RetryRequest) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
var err error
for i := 0; i < retry.maxRetries+1; i++ {
err = retry.connection.Make(request, passedResponse)
if err == nil {
return nil
}
if skipRetry(request.Method, passedResponse.HTTPResponse) {
break
}
if request.Body == nil {
continue
}
// detect if body is ioutil.NopCloser(&bytes.Buffer)
// if so we reset the content the buffer to be able to redo request with same body
if reflect.TypeOf(request.Body) == reflect.TypeOf(io.NopCloser) {
reader := reflect.ValueOf(request.Body).FieldByName("Reader")
if buf, ok := reader.Interface().(*bytes.Buffer); ok {
data := buf.Bytes()
buf.Reset()
buf.Write(data)
}
continue
}
// detect if body is implementing interface ReadSeeker
// if so we go to the beginning of the content to be able to redo request with same body
if reader, ok := request.Body.(io.ReadSeeker); ok {
_, resetErr := reader.Seek(0, 0)
if resetErr != nil {
var pipeSeekError ccerror.PipeSeekError
if errors.As(resetErr, &pipeSeekError) {
return ccerror.PipeSeekError{Err: err}
}
return resetErr
}
continue
}
// if we reach this part, we are not able to know what is inside request body (and be able to resend the same content).
// This probably cause of full stream send which can be necessary by user.
// so we return directly the current error
return err
}
return err
}
// Wrap sets the connection in the RetryRequest and returns itself.
func (retry *RetryRequest) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
retry.connection = innerconnection
return retry
}
func skipRetry(httpMethod string, response *http.Response) bool {
return httpMethod == http.MethodPost ||
response != nil &&
response.StatusCode != http.StatusInternalServerError &&
response.StatusCode != http.StatusBadGateway &&
response.StatusCode != http.StatusServiceUnavailable &&
response.StatusCode != http.StatusGatewayTimeout
}
type retryRequestRouter struct {
maxRetries int
connection router.Connection
}
func newRetryRequestRouter(maxRetries int) *retryRequestRouter {
return &retryRequestRouter{
maxRetries: maxRetries,
}
}
func (retry *retryRequestRouter) Make(request *router.Request, passedResponse *router.Response) error {
var err error
for i := 0; i < retry.maxRetries+1; i++ {
err = retry.connection.Make(request, passedResponse)
if err == nil {
return nil
}
if skipRetry(request.Method, passedResponse.HTTPResponse) && passedResponse.HTTPResponse.StatusCode != http.StatusNotFound {
break
}
if request.Body == nil {
continue
}
// detect if body is ioutil.NopCloser(&bytes.Buffer)
// if so we reset the content the buffer to be able to redo request with same body
if reflect.TypeOf(request.Body) == reflect.TypeOf(io.NopCloser) {
reader := reflect.ValueOf(request.Body).FieldByName("Reader")
if buf, ok := reader.Interface().(*bytes.Buffer); ok {
data := buf.Bytes()
buf.Reset()
buf.Write(data)
}
continue
}
// detect if body is implementing interface ReadSeeker
// if so we go to the beginning of the content to be able to redo request with same body
if reader, ok := request.Body.(io.ReadSeeker); ok {
_, resetErr := reader.Seek(0, 0)
if resetErr != nil {
var pipeSeekError ccerror.PipeSeekError
if errors.As(resetErr, &pipeSeekError) {
return ccerror.PipeSeekError{Err: err}
}
return resetErr
}
continue
}
// if we reach this part, we are not able to know what is inside request body (and be able to resend the same content).
// This probably cause of full stream send which can be necessary by user.
// so we return directly the current error
return err
}
return err
}
// Wrap sets the connection in the RetryRequest and returns itself.
func (retry *retryRequestRouter) Wrap(innerconnection router.Connection) router.Connection {
retry.connection = innerconnection
return retry
}