Проблемы с совместной работой Resilience4j Retry и java.net.http.HttpClient

Я пытаюсь получить базовый httpclient httprequest httpresponse, работающий с Resilience4j Retry.

Дословный код из: https://resilience4j.readme.io/docs/retry

  RetryConfig config = RetryConfig.custom()
  .maxAttempts(5)
  .waitDuration(Duration.ofMillis(1000))
  .retryOnResult(response -> response.getStatus() == 500)
  .retryOnException(e -> e instanceof WebServiceException)
  .retryExceptions(IOException.class, TimeoutException.class)
  .ignoreExceptions(BusinessException.class, OtherBusinessException.class)
  .build();

// Create a RetryRegistry with a custom global configuration
RetryRegistry registry = RetryRegistry.of(config);

// Get or create a Retry from the registry - 
// Retry will be backed by the default config
Retry retryWithDefaultConfig = registry.retry("name1");

Обратите внимание, что в приведенном выше коде отсутствует определение общего T, например:

  RetryConfig config = RetryConfig.<MyConcrete>custom()

и дословный код из: https://resilience4j.readme.io/docs/examples

Supplier<String> supplierWithResultAndExceptionHandler = SupplierUtils
  .andThen(supplier, (result, exception) -> "Hello Recovery");

Supplier<HttpResponse> supplier = () -> httpClient.doRemoteCall();
Supplier<HttpResponse> supplierWithResultHandling = SupplierUtils.andThen(supplier, result -> {
    if (result.getStatusCode() == 400) {
       throw new ClientException();
    } else if (result.getStatusCode() == 500) {
       throw new ServerException();
    }
    return result;
});
HttpResponse httpResponse = circuitBreaker
  .executeSupplier(supplierWithResultHandling);

======

Итак, используя эти две частички, я придумал следующее.

Обратите внимание: я использую настоящие java.net.http.HttpClient и java.net.http.HttpResponse (из JDK11)

import io.github.resilience4j.core.SupplierUtils;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

import javax.inject.Inject;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

public final class ResilientHttpClient /* implements IResilientHttpClient */ {

    private static Logger logger;

    private final HttpClient httpClient;

    @Inject
    public ResilientHttpClient(final HttpClient httpClient) {
        this(LoggerFactory
                .getLogger(ResilientHttpClient.class), httpClient);
    }

    /**
     * Constructor, which pre-populates the provider with one resource instance.
     */
    public ResilientHttpClient(final Logger lgr,
                               final HttpClient httpClient) {
        if (null == lgr) {
            throw new IllegalArgumentException("Logger is null");
        }
        this.logger = lgr;

        if (null == httpClient) {
            throw new IllegalArgumentException("HttpClient is null");
        }

        this.httpClient = httpClient;

    }

    public String executeHttpRequest(String circuitbreakerInstanceName, HttpRequest httpRequest) {

        try {

            /* circuitbreakerInstanceName  is future place holder for .yml configuration see : https://resilience4j.readme.io/docs/getting-started-3 */

        RetryConfig config = RetryConfig.<HttpResponse>custom()
                    .waitDuration(Duration.ofMillis(1000))
                    .retryOnResult(response -> response.statusCode() == 500)
                    .retryOnException(e -> e instanceof ArithmeticException)
                    .retryExceptions(IOException.class, TimeoutException.class)
                    //.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
                    .build();

            // Create a RetryRegistry with a custom global configuration
            RetryRegistry registry = RetryRegistry.of(config);

            // Get or create a Retry from the registry -
            // Retry will be backed by the default config
            Retry retryWithDefaultConfig = registry.retry(circuitbreakerInstanceName);

            Supplier<HttpResponse> supplier = () -> this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

            Supplier<String> supplierWithResultAndExceptionHandler = SupplierUtils
                    .andThen(supplier, (result, exception) -> "Hello Recovery");

            Supplier<HttpResponse> supplierWithResultHandling = SupplierUtils.andThen(supplier, result -> {
                if (result.statusCode() == HttpStatus.BAD_REQUEST.value()) {
                    throw new RuntimeException("400");
                } else if (result.statusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
                    throw new RuntimeException("500");
                }
                return result;
            });

            HttpResponse<String> response = retryWithDefaultConfig.executeSupplier(supplierWithResultHandling);

            String responseBody = response.body();

            return responseBody;

        } catch (Exception ex) {
            throw new RuntimeException((ex));
        }
    }

}

У меня проблема:

Линия:

Supplier<HttpResponse> supplier = () - > this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

выдает ошибку (в intelliJ) необработанных исключений IOException, InterruptedException

Итак, изменив метод следующим образом:

 public String executeHttpRequest(String circuitbreakerInstanceName, HttpRequest httpRequest) throws IOException, InterruptedException {

чувствует себя неправильно. Но даже когда я пытаюсь ... ничего не решает. :(

Вероятно, это какое-то вуду с проверенными исключениями с помощью ламды.

Но ближе к делу:

Так что я не знаю, верен ли способ, которым я объединил две частица. В полнофункциональной области образцов немного не хватает.

Спасибо за любую помощь. Выполнить базовую попытку httpclient несколько раз не должно быть слишком сложно. Но я бьюсь головой о стену.

Мои зависимости от Gradle.

dependencies {

    implementation group: 'javax.inject', name: 'javax.inject', version: javaxInjectVersion
    implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion

    implementation group: 'org.springframework', name: 'spring-web', version: springWebVersion

    implementation "io.github.resilience4j:resilience4j-circuitbreaker:${resilience4jVersion}"
    implementation "io.github.resilience4j:resilience4j-ratelimiter:${resilience4jVersion}"
    implementation "io.github.resilience4j:resilience4j-retry:${resilience4jVersion}"
    implementation "io.github.resilience4j:resilience4j-bulkhead:${resilience4jVersion}"
    implementation "io.github.resilience4j:resilience4j-cache:${resilience4jVersion}"
    implementation "io.github.resilience4j:resilience4j-timelimiter:${resilience4jVersion}"


    testCompile group: 'junit', name: 'junit', version: junitVersion
}

а также

   resilience4jVersion = '1.5.0'
    slf4jVersion = "1.7.30"
    javaxInjectVersion = "1"
 springWebVersion = '5.2.8.RELEASE'
    junitVersion = "4.12"

person granadaCoder    schedule 18.09.2020    source источник


Ответы (1)


просто из интереса:

  • Какую версию Java вы используете? Java 11?
  • Почему вы не можете использовать Spring Boot? Стартер Resilience4j Spring Boot значительно упрощает настройку.

Если вы настроили retryOnResult(response -> response.getStatus() == 500), вам больше не нужно использовать SupplierUtils для сопоставления HttpResponse с определенным кодом состояния с исключением времени выполнения.

RetryConfig config = RetryConfig.<HttpResponse<String>>custom()
            .waitDuration(Duration.ofMillis(1000))
            .retryOnResult(response -> response.statusCode() == 500)
            .retryExceptions(IOException.class, TimeoutException.class)
            .build();

Пожалуйста, не создавайте реестры и конфигурации внутри executeHttpRequest, а вставляйте их в свой конструктор.

Вы можете создать такой статический метод:

public static <T> HttpResponse<T> executeHttpRequest(Callable<HttpResponse<T>> callable, Retry retry, CircuitBreaker circuitBreaker) throws Exception {
        return Decorators.ofCallable(callable)
            .withRetry(retry)
            .withCircuitBreaker(circuitBreaker)
            .call();
}

и вызовите метод следующим образом:

HttpResponse<String> response = executeHttpRequest(
    () -> httpClient.send(request, HttpResponse.BodyHandlers.ofString()), 
    retry, 
    circuitBreaker);
person Robert Winkler    schedule 22.09.2020
comment
JDK11. Да, я согласен с введенными им рассказами. На данный момент это мой первый привет-мир. Или у меня будет перегрузка, которая берет retryInstanceName и извлекает его из .yml. Но все это второстепенно по сравнению с повторной попыткой hello-world. - person granadaCoder; 22.09.2020
comment
Позвольте мне поработать с вашим предложением retryOnResult (response - ›response.getStatus () == 500), комментарий сегодня. - person granadaCoder; 22.09.2020
comment
Но поскольку вы разработали какой-то передовой синтаксис, я постараюсь использовать и его. - person granadaCoder; 22.09.2020
comment
Это сработало для вас? Если да, примите ответ - person Robert Winkler; 24.09.2020
comment
Я наконец вернулся к этому. Пробую твой код. С Decorators.ofCallable я не могу найти импорт для декораторов. Любой совет? - person granadaCoder; 08.10.2020