Skip to content

Commit

Permalink
Handle HTTP errors when fetching a nonce
Browse files Browse the repository at this point in the history
The nonce is fetched via HEAD request. Before this fix, if there was a
HTTP error, acme4j expected a Problem JSON body, which was not send
because of the HEAD request, and lead to an AcmeProtocolException.

Now either an AcmeException or AcmeRetryAfterException is thrown.
  • Loading branch information
shred committed May 15, 2024
1 parent aeff120 commit 6d5da63
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ public Optional<Instant> fetch() throws AcmeException {
retryAfterOpt.ifPresent(instant -> LOG.debug("Retry-After: {}", instant));
setRetryAfter(retryAfterOpt.orElse(null));
return retryAfterOpt;
} catch (AcmeRetryAfterException ex) {
LOG.debug("Retry-After while attempting to read the resource", ex);
setRetryAfter(ex.getRetryAfter());
return Optional.of(ex.getRetryAfter());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
Expand Down Expand Up @@ -132,7 +133,12 @@ public void resetNonce(Session session) throws AcmeException {

var rc = getResponse().statusCode();
if (rc != HTTP_OK && rc != HTTP_NO_CONTENT) {
throwAcmeException();
var message = "Server responded with HTTP " + rc + " while trying to retrieve a nonce";
var retryAfterInstant = getRetryAfter();
if (retryAfterInstant.isPresent()) {
throw new AcmeRetryAfterException(message, retryAfterInstant.get());
};
throw new AcmeException(message);
}

session.setNonce(getNonce()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
Expand Down Expand Up @@ -142,6 +143,57 @@ public void testGetNonceFromHeader() throws AcmeException {
verify(getRequestedFor(urlEqualTo(REQUEST_PATH)));
}

/**
* Test that {@link DefaultConnection#getNonce()} handles a retry-after header
* correctly.
*/
@Test
public void testGetNonceFromHeaderRetryAfter() {
var retryAfter = Instant.now().plusSeconds(30L).truncatedTo(SECONDS);

stubFor(head(urlEqualTo(NEW_NONCE_PATH)).willReturn(aResponse()
.withStatus(HttpURLConnection.HTTP_UNAVAILABLE)
.withHeader("Content-Type", "application/problem+json")
.withHeader("Retry-After", DATE_FORMATTER.format(retryAfter))
// do not send a body here because it is a HEAD request!
));

assertThat(session.getNonce()).isNull();

var ex = assertThrows(AcmeRetryAfterException.class, () -> {
try (var conn = session.connect()) {
conn.resetNonce(session);
}
});
assertThat(ex.getMessage()).isEqualTo("Server responded with HTTP 503 while trying to retrieve a nonce");
assertThat(ex.getRetryAfter()).isEqualTo(retryAfter);

verify(headRequestedFor(urlEqualTo(NEW_NONCE_PATH)));
}

/**
* Test that {@link DefaultConnection#getNonce()} handles a general HTTP error
* correctly.
*/
@Test
public void testGetNonceFromHeaderHttpError() {
stubFor(head(urlEqualTo(NEW_NONCE_PATH)).willReturn(aResponse()
.withStatus(HttpURLConnection.HTTP_INTERNAL_ERROR)
// do not send a body here because it is a HEAD request!
));

assertThat(session.getNonce()).isNull();

var ex = assertThrows(AcmeException.class, () -> {
try (var conn = session.connect()) {
conn.resetNonce(session);
}
});
assertThat(ex.getMessage()).isEqualTo("Server responded with HTTP 500 while trying to retrieve a nonce");

verify(headRequestedFor(urlEqualTo(NEW_NONCE_PATH)));
}

/**
* Test that {@link DefaultConnection#getNonce()} fails on an invalid
* {@code Replay-Nonce} header.
Expand Down

0 comments on commit 6d5da63

Please sign in to comment.