Replace exponential back-off in validate with Retry-After header.

Last paragraph of ACME spec, section 6.5:

  To check on the status of an authorization, the client sends a GET
  request to the authorization URI, and the server responds with the
  current authorization object.  In responding to poll requests while
  the validation is still in progress, the server MUST return a 202
  (Accepted) response with a Retry-After header field.
This commit is contained in:
Tommie Gannert 2015-12-05 21:32:53 +00:00
parent b2c88d7a5d
commit 71624f607a
2 changed files with 15 additions and 22 deletions

View file

@ -77,7 +77,7 @@ func NewClient(caDirURL string, user User, keyBits int, optSolvers []string) (*C
} }
var dir directory var dir directory
if err := getJSON(caDirURL, &dir); err != nil { if _, err := getJSON(caDirURL, &dir); err != nil {
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err) return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
} }
@ -634,23 +634,16 @@ func parseLinks(links []string) map[string]string {
return linkMap return linkMap
} }
var (
pollInterval = 1 * time.Second
maxPollInterval = 15 * time.Minute
)
// validate makes the ACME server start validating a // validate makes the ACME server start validating a
// challenge response, only returning once it is done. // challenge response, only returning once it is done.
func validate(j *jws, uri string, chlng challenge) error { func validate(j *jws, uri string, chlng challenge) error {
var challengeResponse challenge var challengeResponse challenge
_, err := postJSON(j, uri, chlng, &challengeResponse) hdr, err := postJSON(j, uri, chlng, &challengeResponse)
if err != nil { if err != nil {
return err return err
} }
delay := pollInterval
// After the path is sent, the ACME server will access our server. // After the path is sent, the ACME server will access our server.
// Repeatedly check the server for an updated status on our request. // Repeatedly check the server for an updated status on our request.
for { for {
@ -666,14 +659,16 @@ func validate(j *jws, uri string, chlng challenge) error {
return errors.New("The server returned an unexpected state.") return errors.New("The server returned an unexpected state.")
} }
// Poll with exponential back-off. ra, err := strconv.Atoi(hdr.Get("Retry-After"))
time.Sleep(delay) if err != nil {
delay *= 2 // The ACME server MUST return a Retry-After.
if delay > maxPollInterval { // If it doesn't, we'll just poll hard.
delay = maxPollInterval ra = 1
} }
time.Sleep(time.Duration(ra) * time.Second)
if err := getJSON(uri, &challengeResponse); err != nil { hdr, err = getJSON(uri, &challengeResponse)
if err != nil {
return err return err
} }
} }
@ -683,18 +678,18 @@ func validate(j *jws, uri string, chlng challenge) error {
// getJSON performs an HTTP GET request and parses the response body // getJSON performs an HTTP GET request and parses the response body
// as JSON, into the provided respBody object. // as JSON, into the provided respBody object.
func getJSON(uri string, respBody interface{}) error { func getJSON(uri string, respBody interface{}) (http.Header, error) {
resp, err := http.Get(uri) resp, err := http.Get(uri)
if err != nil { if err != nil {
return fmt.Errorf("failed to get %q: %v", uri, err) return nil, fmt.Errorf("failed to get %q: %v", uri, err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest { if resp.StatusCode >= http.StatusBadRequest {
return handleHTTPError(resp) return resp.Header, handleHTTPError(resp)
} }
return json.NewDecoder(resp.Body).Decode(respBody) return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
} }
// postJSON performs an HTTP POST request and parses the response body // postJSON performs an HTTP POST request and parses the response body

View file

@ -84,13 +84,11 @@ func TestNewClientOptPort(t *testing.T) {
} }
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
// Disable polling delay in validate for faster tests.
pollInterval = 0
var statuses []string var statuses []string
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Minimal stub ACME server for validation. // Minimal stub ACME server for validation.
w.Header().Add("Replay-Nonce", "12345") w.Header().Add("Replay-Nonce", "12345")
w.Header().Add("Retry-After", "0")
switch r.Method { switch r.Method {
case "HEAD": case "HEAD":
case "POST": case "POST":