@@ -32,17 +32,18 @@ const (
3232 DefaultMaxRedirects = 5
3333 DefaultMaxIdleConns = 20
3434 DefaultMaxIdleConnsPerHost = 20
35- DefaultMaxConnsPerHost = 0 // unlimited
35+ DefaultMaxConnsPerHost = 100
3636 DefaultIdleConnTimeout = 90 * time .Second
3737 DefaultTLSHandshakeTimeout = 5 * time .Second
38- DefaultResponseHeaderTimeout = 0 // use Client.Timeout instead
38+ DefaultResponseHeaderTimeout = 10 * time . Second
3939 DefaultDialTimeout = 5 * time .Second
4040 DefaultDialKeepAlive = 30 * time .Second
4141 DefaultMaxRetries = 3
4242 DefaultRetryWaitMin = 250 * time .Millisecond
4343 DefaultRetryWaitMax = 2 * time .Second
4444 DefaultExpectContinueTimeout = 2 * time .Second
4545 DefaultMaxRetryBodyBytes = 4 << 20 // 4 MiB
46+ DefaultMaxRetryAfter = 1 * time .Minute
4647)
4748
4849type config struct {
@@ -69,6 +70,7 @@ type config struct {
6970 maxRetries int
7071 retryWaitMin time.Duration
7172 retryWaitMax time.Duration
73+ maxRetryAfter time.Duration
7274 maxRetryBodyBytes int64
7375 retryableStatusCodes map [int ]struct {}
7476 retryableMethods map [string ]struct {}
@@ -99,6 +101,7 @@ func defaults() *config {
99101 maxRetries : DefaultMaxRetries ,
100102 retryWaitMin : DefaultRetryWaitMin ,
101103 retryWaitMax : DefaultRetryWaitMax ,
104+ maxRetryAfter : DefaultMaxRetryAfter ,
102105 maxRetryBodyBytes : DefaultMaxRetryBodyBytes ,
103106 retryableStatusCodes : map [int ]struct {}{
104107 http .StatusTooManyRequests : {},
@@ -119,18 +122,16 @@ type Option func(*config)
119122
120123// New creates a new *http.Client with good defaults.
121124// All defaults can be overridden via Option values.
125+ // As with all http.Clients, be sure to use the returned
126+ // client across the lifetime of multiple requests.
122127func New (opts ... Option ) * http.Client {
123128 cfg := defaults ()
124129 for _ , opt := range opts {
125130 opt (cfg )
126131 }
127132
128- if cfg .maxRetries < 0 {
129- cfg .maxRetries = 0
130- }
131- if cfg .maxRedirects < 0 {
132- cfg .maxRedirects = 0
133- }
133+ cfg .maxRetries = max (cfg .maxRetries , 0 )
134+ cfg .maxRedirects = max (cfg .maxRedirects , 0 )
134135 if cfg .retryWaitMin <= 0 {
135136 cfg .retryWaitMin = DefaultRetryWaitMin
136137 }
@@ -140,6 +141,19 @@ func New(opts ...Option) *http.Client {
140141 if cfg .retryWaitMin > cfg .retryWaitMax {
141142 cfg .retryWaitMin , cfg .retryWaitMax = cfg .retryWaitMax , cfg .retryWaitMin
142143 }
144+ if cfg .maxRetryAfter <= 0 {
145+ cfg .maxRetryAfter = DefaultMaxRetryAfter
146+ }
147+
148+ // Clamp negative durations for transport-level settings.
149+ // Zero is valid and means "no limit" for most of these.
150+ cfg .timeout = max (cfg .timeout , 0 )
151+ cfg .dialTimeout = max (cfg .dialTimeout , 0 )
152+ cfg .dialKeepAlive = max (cfg .dialKeepAlive , 0 )
153+ cfg .idleConnTimeout = max (cfg .idleConnTimeout , 0 )
154+ cfg .tlsHandshakeTimeout = max (cfg .tlsHandshakeTimeout , 0 )
155+ cfg .responseHeaderTimeout = max (cfg .responseHeaderTimeout , 0 )
156+ cfg .expectContinueTimeout = max (cfg .expectContinueTimeout , 0 )
143157
144158 var base http.RoundTripper
145159 if cfg .transport != nil {
@@ -148,6 +162,8 @@ func New(opts ...Option) *http.Client {
148162 tlsCfg := cfg .tlsConfig
149163 if tlsCfg == nil {
150164 tlsCfg = & tls.Config {}
165+ } else {
166+ tlsCfg = tlsCfg .Clone ()
151167 }
152168 if tlsCfg .MinVersion < tls .VersionTLS12 {
153169 tlsCfg .MinVersion = tls .VersionTLS12
@@ -177,6 +193,7 @@ func New(opts ...Option) *http.Client {
177193 maxRetries : cfg .maxRetries ,
178194 waitMin : cfg .retryWaitMin ,
179195 waitMax : cfg .retryWaitMax ,
196+ maxRetryAfter : cfg .maxRetryAfter ,
180197 maxRetryBodyBytes : cfg .maxRetryBodyBytes ,
181198 retryableCodes : cfg .retryableStatusCodes ,
182199 retryableMethods : cfg .retryableMethods ,
@@ -243,7 +260,7 @@ func WithMaxIdleConnsPerHost(n int) Option {
243260}
244261
245262// WithMaxConnsPerHost sets the maximum total connections per host.
246- // 0 means unlimited (the default) .
263+ // 0 means unlimited. Default: 100 .
247264func WithMaxConnsPerHost (n int ) Option {
248265 return func (c * config ) { c .maxConnsPerHost = n }
249266}
@@ -261,7 +278,7 @@ func WithTLSHandshakeTimeout(d time.Duration) Option {
261278}
262279
263280// WithResponseHeaderTimeout sets the maximum time to wait for response headers
264- // after the request is fully written. 0 means no limit (the default) .
281+ // after the request is fully written. 0 means no limit. Default: 10s .
265282func WithResponseHeaderTimeout (d time.Duration ) Option {
266283 return func (c * config ) { c .responseHeaderTimeout = d }
267284}
@@ -278,9 +295,14 @@ func WithRetries(n int) Option {
278295 return func (c * config ) { c .maxRetries = n }
279296}
280297
298+ // WithNoRetries disables retry logic entirely.
299+ func WithNoRetries () Option {
300+ return WithRetries (0 )
301+ }
302+
281303// WithRetryWait sets the minimum and maximum wait times between retries.
282304// Backoff is exponential with full jitter within these bounds.
283- // Default: 1s min, 10s max.
305+ // Default: 250ms min, 2s max.
284306func WithRetryWait (minWait , maxWait time.Duration ) Option {
285307 return func (c * config ) {
286308 c .retryWaitMin = minWait
@@ -334,6 +356,14 @@ func WithAdditionalRetryableMethods(methods ...string) Option {
334356 }
335357}
336358
359+ // WithMaxRetryAfter sets the maximum duration that a server's Retry-After
360+ // header will be respected. If the server requests a longer delay, it will
361+ // be capped at this value. Retry-After values are also floored at the
362+ // minimum retry wait time (see WithRetryWait). Default: 1 minute.
363+ func WithMaxRetryAfter (d time.Duration ) Option {
364+ return func (c * config ) { c .maxRetryAfter = d }
365+ }
366+
337367// WithMaxRetryBodyBytes sets the maximum request body size (in bytes) that will
338368// be buffered into memory for retry support. Bodies larger than this limit cause
339369// an error when retries are enabled and the body is not already seekable.
@@ -350,9 +380,11 @@ func WithCheckRetry(fn func(req *http.Request, resp *http.Response, err error) b
350380 return func (c * config ) { c .checkRetry = fn }
351381}
352382
353- // WithRetryObserver registers a callback that is invoked each time a retry
354- // decision is made. The attempt number is 0-indexed (0 = first attempt that
355- // will be retried). This is useful for logging or metrics.
383+ // WithRetryObserver registers a callback that is invoked before each retry
384+ // attempt. The attempt number is 0-indexed (0 = first failed attempt that
385+ // will be retried). This is not called on the final exhausted attempt —
386+ // only when a retry will actually follow. This is useful for logging or
387+ // metrics.
356388func WithRetryObserver (fn func (attempt int , req * http.Request , resp * http.Response , err error )) Option {
357389 return func (c * config ) { c .retryObserver = fn }
358390}
0 commit comments