1515package client
1616
1717import (
18+ "crypto/tls"
19+ "errors"
20+ "fmt"
1821 "net/http"
22+ "net/url"
23+ "regexp"
24+ "strings"
1925 "time"
2026
21- "github.com/hashicorp/go-retryablehttp"
27+ "github.com/avast/retry-go/v4"
28+ "go.uber.org/zap"
2229)
2330
2431// Option is a functional option for customizing static signatures.
@@ -30,7 +37,7 @@ type options struct {
3037 RetryWaitMin time.Duration
3138 RetryWaitMax time.Duration
3239 InsecureTLS bool
33- Logger interface {}
40+ Logger * zap. SugaredLogger
3441 NoDisableKeepalives bool
3542 Headers map [string ][]string
3643}
@@ -81,12 +88,11 @@ func WithRetryWaitMax(t time.Duration) Option {
8188 }
8289}
8390
84- // WithLogger sets the logger; it must implement either retryablehttp.Logger or retryablehttp.LeveledLogger ; if not, this will not take effect.
91+ // WithLogger sets the logger; it must be a *zap.SugaredLogger ; if not, this will not take effect.
8592func WithLogger (logger interface {}) Option {
8693 return func (o * options ) {
87- switch logger .(type ) {
88- case retryablehttp.Logger , retryablehttp.LeveledLogger :
89- o .Logger = logger
94+ if l , ok := logger .(* zap.SugaredLogger ); ok {
95+ o .Logger = l
9096 }
9197 }
9298}
@@ -113,33 +119,75 @@ func WithHeaders(h map[string][]string) Option {
113119}
114120
115121type roundTripper struct {
116- http.RoundTripper
117- UserAgent string
118- Headers map [string ][]string
122+ inner http.RoundTripper
123+ * options
119124}
120125
121126// RoundTrip implements `http.RoundTripper`
122- func (rt * roundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
123- req .Header .Set ("User-Agent" , rt .UserAgent )
124- for k , v := range rt .Headers {
127+ func (rt * roundTripper ) RoundTrip (req * http.Request ) (res * http.Response , err error ) {
128+ req .Header .Set ("User-Agent" , rt .options . UserAgent )
129+ for k , v := range rt .options . Headers {
125130 for _ , h := range v {
126131 req .Header .Add (k , h )
127132 }
128133 }
129- return rt .RoundTripper .RoundTrip (req )
134+
135+ err = retry .Do (func () (err error ) {
136+ res , err = rt .inner .RoundTrip (req )
137+ if retryErr := shouldRetry (res , err ); retryErr != nil {
138+ return retryErr
139+ }
140+ return nil
141+ },
142+ retry .Attempts (rt .options .RetryCount ),
143+ retry .Delay (rt .options .RetryWaitMin ),
144+ retry .MaxDelay (rt .options .RetryWaitMax ),
145+ )
146+
147+ return res , err
148+ }
149+
150+ var tooManyRedirectyRe = regexp .MustCompile (`stopped after \d+ redirects\z` )
151+
152+ func shouldRetry (resp * http.Response , err error ) error {
153+ if err != nil {
154+ urlErr := & url.Error {}
155+
156+ // Filter well known URL errors
157+ if errors .As (err , & urlErr ) {
158+ certVerificationErr := & tls.CertificateVerificationError {}
159+
160+ if tooManyRedirectyRe .MatchString (urlErr .Error ()) ||
161+ strings .Contains (urlErr .Error (), "unsupported protocol scheme" ) ||
162+ strings .Contains (urlErr .Error (), "invalid header" ) ||
163+ strings .Contains (urlErr .Error (), "certificate is not trusted" ) ||
164+ errors .As (urlErr .Err , & certVerificationErr ) {
165+ return nil
166+ }
167+ }
168+
169+ // Retry any other errror
170+ return err
171+ }
172+
173+ if resp .StatusCode == http .StatusTooManyRequests {
174+ return fmt .Errorf ("retry %d: %s" , resp .StatusCode , resp .Status )
175+ }
176+
177+ if resp .StatusCode == 0 || (resp .StatusCode >= 500 &&
178+ resp .StatusCode != http .StatusNotImplemented ) {
179+ return fmt .Errorf ("retry unexpected HTTP status %d: %s" , resp .StatusCode , resp .Status )
180+ }
181+
182+ return nil
130183}
131184
132- func createRoundTripper (inner http.RoundTripper , o * options ) http.RoundTripper {
185+ func wrapRoundTripper (inner http.RoundTripper , o * options ) http.RoundTripper {
133186 if inner == nil {
134187 inner = http .DefaultTransport
135188 }
136- if o .UserAgent == "" && o .Headers == nil {
137- // There's nothing to do...
138- return inner
139- }
140189 return & roundTripper {
141- RoundTripper : inner ,
142- UserAgent : o .UserAgent ,
143- Headers : o .Headers ,
190+ inner : inner ,
191+ options : o ,
144192 }
145193}
0 commit comments