// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package acme import ( "bytes" "context" "crypto" "crypto/rand" "encoding/json" "errors" "fmt" "io" "math/big" "net/http" "runtime/debug" "strconv" "strings" "time" ) // retryTimer encapsulates common logic for retrying unsuccessful requests. // It is not safe for concurrent use. type retryTimer struct { // backoffFn provides backoff delay sequence for retries. // See Client.RetryBackoff doc comment. backoffFn func(n int, r *http.Request, res *http.Response) time.Duration // n is the current retry attempt. n int } func (t *retryTimer) inc() { t.n++ } // backoff pauses the current goroutine as described in Client.RetryBackoff. func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error { d := t.backoffFn(t.n, r, res) if d <= 0 { return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n) } wakeup := time.NewTimer(d) defer wakeup.Stop() select { case <-ctx.Done(): return ctx.Err() case <-wakeup.C: return nil } } func (c *Client) retryTimer() *retryTimer { f := c.RetryBackoff if f == nil { f = defaultBackoff } return &retryTimer{backoffFn: f} } // defaultBackoff provides default Client.RetryBackoff implementation // using a truncated exponential backoff algorithm, // as described in Client.RetryBackoff. // // The n argument is always bounded between 1 and 30. // The returned value is always greater than 0. func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration { const maxVal = 10 * time.Second var jitter time.Duration if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil { // Set the minimum to 1ms to avoid a case where // an invalid Retry-After value is parsed into 0 below, // resulting in the 0 returned value which would unintentionally // stop the retries. jitter = (1 + time.Duration(x.Int64())) * time.Millisecond } if v, ok := res.Header["Retry-After"]; ok { return retryAfter(v[0]) + jitter } if n < 1 { n = 1 } if n > 30 { n = 30 } d := time.Duration(1<= 500 || code == http.StatusTooManyRequests } // responseError creates an error of Error type from resp. func responseError(resp *http.Response) error { // don't care if ReadAll returns an error: // json.Unmarshal will fail in that case anyway b, _ := io.ReadAll(resp.Body) e := &wireError{Status: resp.StatusCode} if err := json.Unmarshal(b, e); err != nil { // this is not a regular error response: // populate detail with anything we received, // e.Status will already contain HTTP response code value e.Detail = string(b) if e.Detail == "" { e.Detail = resp.Status } } return e.error(resp.Header) }