Выбрать главу

Mono<Ingredient> ingredientMono = webClient

   .get()

   .uri("http://localhost:8080/ingredients/{id}", ingredientId)

   .retrieve()

   .bodyToMono(Ingredient.class);

До тех пор значение в идентификаторе ингредиента соответствует известному ресурсу ингредиента, то результирующий Mono опубликует объект ингредиента, когда он будет подписан. Но что произойдет, если не будет подходящего ингредиента?

При подписке на Mono или Flux, который может закончиться ошибкой, важно зарегистрировать получателя ошибок, а также получателя данных в вызове метода subscribe():

ingredientMono.subscribe(

ingredient -> {

// обрабатывать данные ингредиента

...

},

error-> {

// разобраться с ошибкой

...

});

Если ресурс ингредиента найден, то первая лямбда (потребитель данных), предоставленная subscribe(), вызывается с соответствующим объектом ингредиента. Но если он не найден, то запрос отвечает кодом состояния HTTP 404 (не найден), что приводит к тому, что вторая лямбда (потребитель ошибок) по умолчанию получает исключение WebClientResponseException.

Самая большая проблема с WebClientResponseException заключается в том, что он довольно неспецифичен относительно того, что могло пойти не так, чтобы вызвать сбой Mono. Его название предполагает, что была ошибка в ответе на запрос, сделанный WebClient, но вам нужно изучить исключение WebClientResponseException, чтобы узнать, что пошло не так. И в любом случае было бы неплохо, если бы исключение, предоставленное потребителю ошибок, было более специфичным для домена, а не для WebClient.

Добавляя пользовательский обработчик ошибок, вы можете предоставить код, который преобразует код состояния в Throwable по вашему выбору. Предположим, что вы хотите, чтобы неудачный запрос для ресурса ингредиента привел к тому, что Mono завершилось ошибкой с UnknownIngredientException. Вы можете добавить вызов onStatus() после вызова retrieve(), чтобы добиться этого:

Mono<Ingredient> ingredientMono = webClient

   .get()

   .uri("http://localhost:8080/ingredients/{id}", ingredientId)

   .retrieve()

   .onStatus(HttpStatus::is4xxClientError,

   response -> Mono.just(new UnknownIngredientException()))

   .bodyToMono(Ingredient.class);

Первый аргумент в вызове onStatus() - это предикат, задающий состояние Http и возвращающий значение true, если требуется обработать код состояния. И если код состояния совпадает, то ответ будет возвращен функции во втором аргументе для обработки, как он считает нужным, в конечном итоге возвращая Mono типа Throwable.

В этом примере, если код состояния представляет собой код состояния уровня 400 (например, ошибка клиента), Mono будет возвращен с UnknownIngredientException.  Это приводит к тому, что ingredientMono терпит неудачу с этим исключением.

Обратите внимание, что HttpStatus::is4xxClientError - это ссылка на метод is4xxClientError метода HttpStatus. Именно этот метод будет вызван для данного HttpStatus объекта. Если вы хотите, вы можете использовать другой метод в HttpStatus в качестве ссылки на метод; или вы можете предоставить свою собственную функцию в виде ссылки на лямбду или метод, которая возвращает boolean.

Например, вы можете получить еще более точную обработку ошибок, специально проверив состояние HTTP 404 (НЕ НАЙДЕНО), изменив вызов onStatus(), чтобы он выглядел следующим образом:

Mono<Ingredient> ingredientMono = webClient

   .get()

   .uri("http://localhost:8080/ingredients/{id}", ingredientId)

   .retrieve()

   .onStatus(status -> status == HttpStatus.NOT_FOUND,

      response -> Mono.just(new UnknownIngredientException()))

   .bodyToMono(Ingredient.class);

Стоит также отметить, что вы можете иметь столько вызовов onStatus(), сколько вам нужно для обработки любых кодов состояния HTTP, которые могут возвращаться в ответе.

11.4.5 Обмен запросами

До этого момента вы использовали метод retrieve() для обозначения отправки запроса при работе с WebClient. В этих случаях метод retrieve() возвращает объект типа ResponseSpec, с помощью которого можно обрабатывать ответ с вызовами таких методов, как onStatus(), bodyToFlux() и bodyToMono(). Работа со спецификацией ответа хороша для простых случаев, но она несколько ограничена. Например, если вам нужен доступ к заголовкам ответа или значениям cookie, ResponseSpec вам не подойдет.

Когда ResponseSpec заканчивается, вы можете попробовать вызвать exchange() вместо retrieve(). Метод exchange() возвращает Mono типа ClientResponse, к которому можно применить реактивные операции для проверки и использования данных из всего ответа, включая полезную нагрузку, заголовки и файлы cookie.

Прежде чем мы рассмотрим, что отличает exchange() от retrieve(), давайте начнем с того, насколько они похожи. Следующий фрагмент кода использует WebClient и exchange() для извлечения одного ингредиента по идентификатору:

Mono<Ingredient> ingredientMono = webClient

   .get()

   .uri("http://localhost:8080/ingredients/{id}", ingredientId)

   .exchange()

   .flatMap(cr -> cr.bodyToMono(Ingredient.class));