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)); | ||||
|             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()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -51,6 +51,7 @@ import org.shredzone.acme4j.exception.AcmeException; | |||
| 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; | ||||
|  | @ -132,7 +133,12 @@ public class DefaultConnection implements Connection { | |||
| 
 | ||||
|             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() | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ import org.shredzone.acme4j.Session; | |||
| 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; | ||||
|  | @ -142,6 +143,57 @@ public class DefaultConnectionTest { | |||
|         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. | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Richard Körber
						Richard Körber