Skip to content

Commit 4225410

Browse files
committed
Implement an http transport that retries after a rate limit.
Wait until the reset time expires and try again depending on the pending attempts. Signed-off-by: David Calavera <[email protected]>
1 parent 2c23c4b commit 4225410

File tree

6 files changed

+261
-498
lines changed

6 files changed

+261
-498
lines changed

go/Gopkg.lock

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@
4848
[[constraint]]
4949
name = "github.com/sirupsen/logrus"
5050
version = "1.0.3"
51+
52+
[[constraint]]
53+
name = "github.com/Azure/go-autorest"
54+
version = "v9.6.0"

go/porcelain/http/http.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package http
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
"time"
7+
8+
"github.com/Azure/go-autorest/autorest"
9+
"github.com/go-openapi/runtime"
10+
)
11+
12+
type RetryableTransport struct {
13+
tr runtime.ClientTransport
14+
attempts int
15+
}
16+
17+
type retryableRoundTripper struct {
18+
tr http.RoundTripper
19+
attempts int
20+
}
21+
22+
func NewRetryableTransport(tr runtime.ClientTransport, attempts int) *RetryableTransport {
23+
return &RetryableTransport{
24+
tr: tr,
25+
attempts: attempts,
26+
}
27+
}
28+
29+
func (tr *RetryableTransport) Submit(op *runtime.ClientOperation) (interface{}, error) {
30+
op.Client.Transport = &retryableRoundTripper{
31+
tr: op.Client.Transport,
32+
attempts: tr.attempts,
33+
}
34+
35+
return tr.Submit(op)
36+
}
37+
38+
func (tr *retryableRoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
39+
rr := autorest.NewRetriableRequest(req)
40+
41+
// Increment to add the first call (attempts denotes number of retries)
42+
tr.attempts++
43+
for attempt := 0; attempt < tr.attempts; attempt++ {
44+
err = rr.Prepare()
45+
if err != nil {
46+
return resp, err
47+
}
48+
resp, err = tr.RoundTrip(rr.Request())
49+
if err != nil || resp.StatusCode != http.StatusTooManyRequests {
50+
return resp, err
51+
}
52+
delayWithRateLimit(resp, req.Cancel)
53+
}
54+
return resp, err
55+
}
56+
57+
func delayWithRateLimit(resp *http.Response, cancel <-chan struct{}) {
58+
retryReset, err := strconv.ParseInt(resp.Header.Get("X-RateLimit-Reset"), 10, 0)
59+
if err != nil {
60+
return
61+
}
62+
63+
t := time.Unix(retryReset, 0)
64+
select {
65+
case <-time.After(t.Sub(time.Now())):
66+
return
67+
case <-cancel:
68+
return
69+
}
70+
}

go/porcelain/netlify_client.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,42 @@ package porcelain
22

33
import (
44
"github.com/go-openapi/runtime"
5+
httptransport "github.com/go-openapi/runtime/client"
56
"github.com/go-openapi/strfmt"
67
"github.com/netlify/open-api/go/plumbing"
8+
"github.com/netlify/open-api/go/porcelain/http"
79
)
810

911
const DefaultSyncFileLimit = 7000
1012
const DefaultConcurrentUploadLimit = 10
13+
const DefaultRetryAttempts = 3
1114

1215
// Default netlify HTTP client.
1316
var Default = NewHTTPClient(nil)
1417

1518
// NewHTTPClient creates a new netlify HTTP client.
1619
func NewHTTPClient(formats strfmt.Registry) *Netlify {
17-
n := plumbing.NewHTTPClient(formats)
18-
return &Netlify{
19-
Netlify: n,
20-
syncFileLimit: DefaultSyncFileLimit,
21-
uploadLimit: DefaultConcurrentUploadLimit,
22-
}
20+
return NewRetryableHTTPClient(formats, DefaultRetryAttempts)
2321
}
2422

25-
// New creates a new netlify client
23+
// NewRetryableHTTPClient creates a new netlify HTTP client with a number of attempts for rate limits.
24+
func NewRetryableHTTPClient(formats strfmt.Registry, attempts int) *Netlify {
25+
cfg := plumbing.DefaultTransportConfig()
26+
transport := httptransport.New(cfg.Host, cfg.BasePath, cfg.Schemes)
27+
28+
return NewRetryable(transport, formats, attempts)
29+
}
30+
31+
// New creates a new netlify client.
2632
func New(transport runtime.ClientTransport, formats strfmt.Registry) *Netlify {
27-
n := plumbing.New(transport, formats)
33+
return NewRetryable(transport, formats, DefaultRetryAttempts)
34+
}
35+
36+
// NewRetryable creates a new netlify client with a number of attempts for rate limits.
37+
func NewRetryable(transport runtime.ClientTransport, formats strfmt.Registry, attempts int) *Netlify {
38+
tr := http.NewRetryableTransport(transport, attempts)
39+
40+
n := plumbing.New(tr, formats)
2841
return &Netlify{
2942
Netlify: n,
3043
syncFileLimit: DefaultSyncFileLimit,

0 commit comments

Comments
 (0)