mirror of https://github.com/shred/acme4j
Handle HTTP errors when fetching a nonce
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.pull/168/head
parent
aeff12088f
commit
6d5da63b8e
|
@ -152,6 +152,10 @@ public abstract class AcmeJsonResource extends AcmeResource {
|
||||||
retryAfterOpt.ifPresent(instant -> LOG.debug("Retry-After: {}", instant));
|
retryAfterOpt.ifPresent(instant -> LOG.debug("Retry-After: {}", instant));
|
||||||
setRetryAfter(retryAfterOpt.orElse(null));
|
setRetryAfter(retryAfterOpt.orElse(null));
|
||||||
return retryAfterOpt;
|
return retryAfterOpt;
|
||||||
|
} catch (AcmeRetryAfterException ex) {
|
||||||
|
LOG.debug("Retry-After while attempting to read the resource", ex);
|
||||||
|
setRetryAfter(ex.getRetryAfter());
|
||||||
|
return Optional.of(ex.getRetryAfter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
|
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.exception.AcmeServerException;
|
import org.shredzone.acme4j.exception.AcmeServerException;
|
||||||
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
||||||
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
|
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
|
||||||
|
@ -132,7 +133,12 @@ public class DefaultConnection implements Connection {
|
||||||
|
|
||||||
var rc = getResponse().statusCode();
|
var rc = getResponse().statusCode();
|
||||||
if (rc != HTTP_OK && rc != HTTP_NO_CONTENT) {
|
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()
|
session.setNonce(getNonce()
|
||||||
|
|
|
@ -52,6 +52,7 @@ import org.shredzone.acme4j.Session;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
|
import org.shredzone.acme4j.exception.AcmeRateLimitedException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.exception.AcmeServerException;
|
import org.shredzone.acme4j.exception.AcmeServerException;
|
||||||
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
|
||||||
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
|
import org.shredzone.acme4j.exception.AcmeUserActionRequiredException;
|
||||||
|
@ -142,6 +143,57 @@ public class DefaultConnectionTest {
|
||||||
verify(getRequestedFor(urlEqualTo(REQUEST_PATH)));
|
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
|
* Test that {@link DefaultConnection#getNonce()} fails on an invalid
|
||||||
* {@code Replay-Nonce} header.
|
* {@code Replay-Nonce} header.
|
||||||
|
|
Loading…
Reference in New Issue