DelayedConnectionLevelRetryHandler implements HttpRequestRetryHandler { @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext ctx) { log.fine(() -> String.format("retryRequest(exception='%s', executionCount='%d', ctx='%s'", exception.getClass().getName(), executionCount, ctx)); HttpClientContext clientCtx = HttpClientContext.adapt(ctx); if (!predicate.test(exception, clientCtx)) { log.fine(() -> String.format("Not retrying for '%s'", ctx)); return false; } if (executionCount > maxRetries) { log.fine(() -> String.format("Max retries exceeded for '%s'", ctx)); retryFailedConsumer.onRetryFailed(exception, executionCount, clientCtx); return false; } Duration delay = delaySupplier.getDelay(executionCount); log.fine(() -> String.format("Retrying after %s for '%s'", delay, ctx)); retryConsumer.onRetry(exception, delay, executionCount, clientCtx); sleeper.sleep(delay); return true; } private  DelayedConnectionLevelRetryHandler(
            DelaySupplier delaySupplier,
            int maxRetries,
            RetryPredicate<IOException> predicate,
            RetryConsumer<IOException> retryConsumer,
            RetryFailedConsumer<IOException> retryFailedConsumer,
            Sleeper sleeper); @Override boolean retryRequest(IOException exception, int executionCount, HttpContext ctx);  }
@SuppressWarnings("unchecked") @Test public void retry_consumers_are_invoked() { RetryConsumer<IOException> retryConsumer = (RetryConsumer<IOException>) mock(RetryConsumer.class); RetryFailedConsumer<IOException> retryFailedConsumer = (RetryFailedConsumer<IOException>) mock(RetryFailedConsumer.class); Duration delay = Duration.ofSeconds(10); int maxRetries = 5; DelayedConnectionLevelRetryHandler handler = DelayedConnectionLevelRetryHandler.Builder .withFixedDelay(delay, maxRetries) .withSleeper(mock(Sleeper.class)) .onRetry(retryConsumer) .onRetryFailed(retryFailedConsumer) .build(); IOException exception = new IOException(); HttpClientContext ctx = new HttpClientContext(); int lastExecutionCount = maxRetries + 1; for (int i = 1; i <= lastExecutionCount; i++) { handler.retryRequest(exception, i, ctx); } verify(retryFailedConsumer).onRetryFailed(exception, lastExecutionCount, ctx); for (int i = 1; i < lastExecutionCount; i++) { verify(retryConsumer).onRetry(exception, delay, i, ctx); } }
@Test public void retry_with_fixed_delay_sleeps_for_expected_duration() { Sleeper sleeper = mock(Sleeper.class); Duration delay = Duration.ofSeconds(2); int maxRetries = 2; DelayedConnectionLevelRetryHandler handler = DelayedConnectionLevelRetryHandler.Builder .withFixedDelay(delay, maxRetries) .withSleeper(sleeper) .build(); IOException exception = new IOException(); HttpClientContext ctx = new HttpClientContext(); int lastExecutionCount = maxRetries + 1; for (int i = 1; i <= lastExecutionCount; i++) { handler.retryRequest(exception, i, ctx); } verify(sleeper, times(2)).sleep(delay); }
@Test public void retry_with_fixed_backoff_sleeps_for_expected_durations() { Sleeper sleeper = mock(Sleeper.class); Duration startDelay = Duration.ofMillis(500); Duration maxDelay = Duration.ofSeconds(5); int maxRetries = 10; DelayedConnectionLevelRetryHandler handler = DelayedConnectionLevelRetryHandler.Builder .withExponentialBackoff(startDelay, maxDelay, maxRetries) .withSleeper(sleeper) .build(); IOException exception = new IOException(); HttpClientContext ctx = new HttpClientContext(); int lastExecutionCount = maxRetries + 1; for (int i = 1; i <= lastExecutionCount; i++) { handler.retryRequest(exception, i, ctx); } verify(sleeper).sleep(startDelay); verify(sleeper).sleep(Duration.ofSeconds(1)); verify(sleeper).sleep(Duration.ofSeconds(2)); verify(sleeper).sleep(Duration.ofSeconds(4)); verify(sleeper, times(6)).sleep(Duration.ofSeconds(5)); }
@Test public void retries_for_listed_exceptions_until_max_retries_exceeded() { int maxRetries = 2; DelayedConnectionLevelRetryHandler handler = DelayedConnectionLevelRetryHandler.Builder .withFixedDelay(Duration.ofSeconds(2), maxRetries) .retryForExceptions(List.of(SSLException.class, ConnectException.class)) .withSleeper(mock(Sleeper.class)) .build(); SSLException sslException = new SSLException("ssl error"); HttpClientContext ctx = new HttpClientContext(); int lastExecutionCount = maxRetries + 1; for (int i = 1; i < lastExecutionCount; i++) { assertTrue(handler.retryRequest(sslException, i, ctx)); } assertFalse(handler.retryRequest(sslException, lastExecutionCount, ctx)); }
@Test public void does_not_retry_for_non_listed_exception() { DelayedConnectionLevelRetryHandler handler = DelayedConnectionLevelRetryHandler.Builder .withFixedDelay(Duration.ofSeconds(2), 2) .retryForExceptions(List.of(SSLException.class, ConnectException.class)) .withSleeper(mock(Sleeper.class)) .build(); IOException ioException = new IOException(); HttpClientContext ctx = new HttpClientContext(); assertFalse(handler.retryRequest(ioException, 1, ctx)); }