aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Azure/go-autorest/autorest/sender.go
blob: 9c0697815bbab6557fcbb276de4a954addad9b56 (plain) (blame)
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
package autorest

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "math"
    "net/http"
    "time"
)

// Sender is the interface that wraps the Do method to send HTTP requests.
//
// The standard http.Client conforms to this interface.
type Sender interface {
    Do(*http.Request) (*http.Response, error)
}

// SenderFunc is a method that implements the Sender interface.
type SenderFunc func(*http.Request) (*http.Response, error)

// Do implements the Sender interface on SenderFunc.
func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) {
    return sf(r)
}

// SendDecorator takes and possibily decorates, by wrapping, a Sender. Decorators may affect the
// http.Request and pass it along or, first, pass the http.Request along then react to the
// http.Response result.
type SendDecorator func(Sender) Sender

// CreateSender creates, decorates, and returns, as a Sender, the default http.Client.
func CreateSender(decorators ...SendDecorator) Sender {
    return DecorateSender(&http.Client{}, decorators...)
}

// DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to
// the Sender. Decorators are applied in the order received, but their affect upon the request
// depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a
// post-decorator (pass the http.Request along and react to the results in http.Response).
func DecorateSender(s Sender, decorators ...SendDecorator) Sender {
    for _, decorate := range decorators {
        s = decorate(s)
    }
    return s
}

// Send sends, by means of the default http.Client, the passed http.Request, returning the
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
// it will apply the http.Client before invoking the Do method.
//
// Send is a convenience method and not recommended for production. Advanced users should use
// SendWithSender, passing and sharing their own Sender (e.g., instance of http.Client).
//
// Send will not poll or retry requests.
func Send(r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
    return SendWithSender(&http.Client{}, r, decorators...)
}

// SendWithSender sends the passed http.Request, through the provided Sender, returning the
// http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which
// it will apply the http.Client before invoking the Do method.
//
// SendWithSender will not poll or retry requests.
func SendWithSender(s Sender, r *http.Request, decorators ...SendDecorator) (*http.Response, error) {
    return DecorateSender(s, decorators...).Do(r)
}

// AfterDelay returns a SendDecorator that delays for the passed time.Duration before
// invoking the Sender. The delay may be terminated by closing the optional channel on the
// http.Request. If canceled, no further Senders are invoked.
func AfterDelay(d time.Duration) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            if !DelayForBackoff(d, 0, r.Cancel) {
                return nil, fmt.Errorf("autorest: AfterDelay canceled before full delay")
            }
            return s.Do(r)
        })
    }
}

// AsIs returns a SendDecorator that invokes the passed Sender without modifying the http.Request.
func AsIs() SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            return s.Do(r)
        })
    }
}

// DoCloseIfError returns a SendDecorator that first invokes the passed Sender after which
// it closes the response if the passed Sender returns an error and the response body exists.
func DoCloseIfError() SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            resp, err := s.Do(r)
            if err != nil {
                Respond(resp, ByDiscardingBody(), ByClosing())
            }
            return resp, err
        })
    }
}

// DoErrorIfStatusCode returns a SendDecorator that emits an error if the response StatusCode is
// among the set passed. Since these are artificial errors, the response body may still require
// closing.
func DoErrorIfStatusCode(codes ...int) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            resp, err := s.Do(r)
            if err == nil && ResponseHasStatusCode(resp, codes...) {
                err = NewErrorWithResponse("autorest", "DoErrorIfStatusCode", resp, "%v %v failed with %s",
                    resp.Request.Method,
                    resp.Request.URL,
                    resp.Status)
            }
            return resp, err
        })
    }
}

// DoErrorUnlessStatusCode returns a SendDecorator that emits an error unless the response
// StatusCode is among the set passed. Since these are artificial errors, the response body
// may still require closing.
func DoErrorUnlessStatusCode(codes ...int) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            resp, err := s.Do(r)
            if err == nil && !ResponseHasStatusCode(resp, codes...) {
                err = NewErrorWithResponse("autorest", "DoErrorUnlessStatusCode", resp, "%v %v failed with %s",
                    resp.Request.Method,
                    resp.Request.URL,
                    resp.Status)
            }
            return resp, err
        })
    }
}

// DoPollForStatusCodes returns a SendDecorator that polls if the http.Response contains one of the
// passed status codes. It expects the http.Response to contain a Location header providing the
// URL at which to poll (using GET) and will poll until the time passed is equal to or greater than
// the supplied duration. It will delay between requests for the duration specified in the
// RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by
// closing the optional channel on the http.Request.
func DoPollForStatusCodes(duration time.Duration, delay time.Duration, codes ...int) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
            resp, err = s.Do(r)

            if err == nil && ResponseHasStatusCode(resp, codes...) {
                r, err = NewPollingRequest(resp, r.Cancel)

                for err == nil && ResponseHasStatusCode(resp, codes...) {
                    Respond(resp,
                        ByDiscardingBody(),
                        ByClosing())
                    resp, err = SendWithSender(s, r,
                        AfterDelay(GetRetryAfter(resp, delay)))
                }
            }

            return resp, err
        })
    }
}

// DoRetryForAttempts returns a SendDecorator that retries a failed request for up to the specified
// number of attempts, exponentially backing off between requests using the supplied backoff
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
// the http.Request.
func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
            for attempt := 0; attempt < attempts; attempt++ {
                resp, err = s.Do(r)
                if err == nil {
                    return resp, err
                }
                DelayForBackoff(backoff, attempt, r.Cancel)
            }
            return resp, err
        })
    }
}

// DoRetryForStatusCodes returns a SendDecorator that retries for specified statusCodes for up to the specified
// number of attempts, exponentially backing off between requests using the supplied backoff
// time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on
// the http.Request.
func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
            b := []byte{}
            if r.Body != nil {
                b, err = ioutil.ReadAll(r.Body)
                if err != nil {
                    return resp, err
                }
            }

            // Increment to add the first call (attempts denotes number of retries)
            attempts++
            for attempt := 0; attempt < attempts; attempt++ {
                r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
                resp, err = s.Do(r)
                if err != nil || !ResponseHasStatusCode(resp, codes...) {
                    return resp, err
                }
                DelayForBackoff(backoff, attempt, r.Cancel)
            }
            return resp, err
        })
    }
}

// DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal
// to or greater than the specified duration, exponentially backing off between requests using the
// supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the
// optional channel on the http.Request.
func DoRetryForDuration(d time.Duration, backoff time.Duration) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (resp *http.Response, err error) {
            end := time.Now().Add(d)
            for attempt := 0; time.Now().Before(end); attempt++ {
                resp, err = s.Do(r)
                if err == nil {
                    return resp, err
                }
                DelayForBackoff(backoff, attempt, r.Cancel)
            }
            return resp, err
        })
    }
}

// WithLogging returns a SendDecorator that implements simple before and after logging of the
// request.
func WithLogging(logger *log.Logger) SendDecorator {
    return func(s Sender) Sender {
        return SenderFunc(func(r *http.Request) (*http.Response, error) {
            logger.Printf("Sending %s %s", r.Method, r.URL)
            resp, err := s.Do(r)
            if err != nil {
                logger.Printf("%s %s received error '%v'", r.Method, r.URL, err)
            } else {
                logger.Printf("%s %s received %s", r.Method, r.URL, resp.Status)
            }
            return resp, err
        })
    }
}

// DelayForBackoff invokes time.After for the supplied backoff duration raised to the power of
// passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set
// to zero for no delay. The delay may be canceled by closing the passed channel. If terminated early,
// returns false.
// Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt
// count.
func DelayForBackoff(backoff time.Duration, attempt int, cancel <-chan struct{}) bool {
    select {
    case <-time.After(time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second):
        return true
    case <-cancel:
        return false
    }
}