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:
parent
b2c88d7a5d
commit
71624f607a
2 changed files with 15 additions and 22 deletions
|
@ -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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
Loading…
Reference in a new issue