Логика повтора Spring WebClient с новыми заголовками

#java #spring #spring-boot #spring-webflux #spring-webclient

Вопрос:

Я пытаюсь построить логику повторных попыток с помощью Spring WebClient . Проблема, которую я пытаюсь решить, очень проста. Я вызываю конечную точку API, чтобы получить некоторые значения. Если API вернет ошибку, скажем, с ответом 401, мне придется позвонить в службу токенов, обновить свой токен, использовать новый токен и выполнить тот же вызов API.

Общее psudo-это

 try {
    GET /locations data
} catch(401 Unauthorized) {
    POST /token and get renew Token --> This is another WebClient API call With New Token
    call again GET /locations and return value
} catch (Another Exception) {
    throw Application Error
}
 

Вот код Spring, который я пытаюсь сделать, и похоже, что он не работает.
Любые предложения о том, как это сделать.

 public List<Location> getLocations(final User user) {
    if (null == user) {
        throw new ApplicationException("User cannot be null");
    }

    if (null == user.getHoneyWellLinkToken()) {
        throw new ApplicationException(String.format("%s has not linked the account with Honeywell", user.getUsername()));
    }


    List<Location> locations = getLocationsAPI(user).block();

    return locations;

}

private Mono<List<Location>> getLocationsAPI(final User user) {
    String endpoint = config.getApi().getLocationsEndpoint()
                .concat("?apikey=")
                .concat(config.getCredentials().getClientId());

    return WebClient.builder().baseUrl(endpoint)
                .build()
                .get()
                .headers(httpHeaders -> httpHeaders.setBearerAuth(user.getHoneyWellLinkToken().getAccessToken()))
                .retrieve()
                .bodyToFlux(Location.class)
                .collectList()
                .doOnError(err -> {
                    WebClient.builder().baseUrl(endpoint)
                            .build()
                            .get()
                            .headers(httpHeaders -> httpHeaders.setBearerAuth(honeywellService.renewToken(user).block().getHoneyWellLinkToken().getAccessToken()))
                            .retrieve().bodyToFlux(Location.class);

                });

}
 

Этот код размещен на GitHub https://github.com/reflexdemon/home-use/blob/main/src/main/java/io/vpv/homeuse/service/HoneywellThermostatService.java

Ответ №1:

  • Используйте onErrorResume вместо doOnError
  • Не block делайте этого при обновлении токена
     private Mono<List<Location>> getLocationsAPI(final User user) {
        String endpoint = config.getApi().getLocationsEndpoint()
                                .concat("?apikey=")
                                .concat(config.getCredentials().getClientId());

        return getLocations(endpoint, user)
            .onErrorResume(err -> honeywellService.renewToken(user)
                                                  .flatMap(newUser -> getLocations(endpoint, newUser)));

    }

    private Mono<List<Location>> getLocations(String endpoint, User user) {
        return WebClient.builder()
                        .baseUrl(endpoint)
                        .build()
                        .get()
                        .headers(httpHeaders -> httpHeaders.setBearerAuth(user
                            .getHoneyWellLinkToken()
                            .getAccessToken()))
                        .retrieve()
                        .bodyToFlux(Location.class)
                        .collectList();
    }
 

Кроме того, рекомендуется использовать один WebClient экземпляр вместо создания нового для каждого запроса.

Комментарии:

1. Спасибо. Мне кажется, я вижу свою проблему. Я работаю над тем, чтобы завершить его в эти выходные, и предоставляю вам свои отзывы.

2. Спасибо. Ваш ответ помог мне двигаться в правильном направлении. Do not block when renewing token У меня было много работы. Видишь github.com/reflexdemon/home-use/pull/1 чтобы увидеть изменения, необходимые для преобразования простого загрузочного приложения в реактивное приложение.