@@ -8,10 +8,12 @@ import (
8
8
"encoding/json"
9
9
"fmt"
10
10
"io"
11
+ "log"
11
12
"net/http"
12
13
"net/url"
13
14
"reflect"
14
15
"sort"
16
+ "strconv"
15
17
"strings"
16
18
"time"
17
19
@@ -20,6 +22,27 @@ import (
20
22
"github.com/pkg/errors"
21
23
)
22
24
25
+ const (
26
+ retryIntervalDefault = 30 * time .Second
27
+ retryStepSeconds = 1
28
+ )
29
+
30
+ var retryStatusCodes = map [int ]struct {}{
31
+ http .StatusTooManyRequests : {},
32
+ }
33
+
34
+ type failFastKey struct {}
35
+
36
+ // WithFailFast disables retries on rate limit.
37
+ func WithFailFast (ctx context.Context ) context.Context {
38
+ return context .WithValue (ctx , failFastKey {}, true )
39
+ }
40
+
41
+ func failFast (ctx context.Context ) bool {
42
+ ok , _ := ctx .Value (failFastKey {}).(bool )
43
+ return ok
44
+ }
45
+
23
46
// httpClient defines an interface for an http.Client implementation so that alternative
24
47
// http Clients can be passed in for making requests
25
48
type httpClient interface {
@@ -267,9 +290,71 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
267
290
return c .NewMultiPartRequestWithContext (context .Background (), method , urlStr , buf )
268
291
}
269
292
293
+ func (c * Client ) Do (req * http.Request , v interface {}) (* Response , error ) {
294
+ return c .doWithDeadline (req , v )
295
+ }
296
+
297
+ func (c * Client ) doWithDeadline (req * http.Request , v interface {}) (* Response , error ) {
298
+ ctx := req .Context ()
299
+ if failFast (ctx ) {
300
+ return c .do (req , v )
301
+ }
302
+
303
+ deadline , deadlineIsSet := ctx .Deadline ()
304
+ if ! deadlineIsSet {
305
+ deadline = time .Now ().Add (retryIntervalDefault )
306
+ dctx , cancel := context .WithDeadline (ctx , deadline )
307
+ defer cancel ()
308
+ ctx = dctx
309
+ req = req .WithContext (ctx )
310
+ }
311
+ errDeadlineWrapped := func (resp * Response , err error ) error {
312
+ return errors .Wrapf (context .DeadlineExceeded , "path: %s, err: %+v, code: %d" , req .URL .String (), err , resp .StatusCode )
313
+ }
314
+
315
+ for {
316
+ select {
317
+ case <- ctx .Done ():
318
+ return nil , ctx .Err ()
319
+ default :
320
+ }
321
+
322
+ resp , err := c .do (req , v )
323
+ if resp != nil {
324
+ if _ , ok := retryStatusCodes [resp .StatusCode ]; ! ok {
325
+ return resp , err
326
+ }
327
+ retrySeconds := retryStepSeconds
328
+ retryAfter := resp .Header .Get ("retry-after" )
329
+ if retryAfter != "" {
330
+ retryAfterSeconds , errConv := strconv .Atoi (retryAfter )
331
+ if errConv != nil {
332
+ log .Println ("failed to parse retry-after:" , err )
333
+ }
334
+ if retryAfterSeconds != 0 {
335
+ retrySeconds = retryAfterSeconds
336
+ }
337
+ }
338
+ retryAfterStep := time .Duration (retrySeconds ) * time .Second
339
+ t := time .NewTimer (retryAfterStep )
340
+ select {
341
+ case <- ctx .Done ():
342
+ t .Stop ()
343
+ return nil , errDeadlineWrapped (resp , err )
344
+ case <- t .C :
345
+ }
346
+ continue
347
+ } else if err != nil {
348
+ return nil , err
349
+ } else {
350
+ return resp , err
351
+ }
352
+ }
353
+ }
354
+
270
355
// Do sends an API request and returns the API response.
271
356
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
272
- func (c * Client ) Do (req * http.Request , v interface {}) (* Response , error ) {
357
+ func (c * Client ) do (req * http.Request , v interface {}) (* Response , error ) {
273
358
httpResp , err := c .client .Do (req )
274
359
if err != nil {
275
360
return nil , err
0 commit comments