Обработка исключений и возврат правильного HTTP-кода с помощью webflux

Я использую функциональные конечные точки WebFlux. Я преобразую исключения, отправленные уровнем сервиса, в код ошибки HTTP, используя onErrorResume:

public Mono<String> serviceReturningMonoError() {
    return Mono.error(new RuntimeException("error"));  
}

public Mono<ServerResponse> handler(ServerRequest request) {
    return serviceReturningMonoError().flatMap(e -> ok().syncBody(e))
                            .onErrorResume( e -> badRequest(e.getMessage()));
}

Он работает хорошо, как только сервис возвращает Mono. Что мне делать, если служба возвращает Flux?

public Flux<String> serviceReturningFluxError() {
    return Flux.error(new RuntimeException("error"));  
}

public Mono<ServerResponse> handler(ServerRequest request) {
    ???
}

Изменить

Я попробовал описанный ниже подход, но, к сожалению, он не работает. Ошибка Flux.error не обрабатывается onErrorResume и не распространяется на фреймворк. Когда исключение распаковывается во время сериализации HTTP-ответа, управление исключениями Spring Boot Exception перехватывает его и преобразует в 500.

public Mono<ServerResponse> myHandler(ServerRequest request) {
      return ok().contentType(APPLICATION_JSON).body( serviceReturningFluxError(), String.class)
             .onErrorResume( exception -> badRequest().build());
}

Я действительно удивлен поведением, это ошибка?


person Nicolas Barbé    schedule 09.02.2018    source источник


Ответы (2)


В вашем первом примере используется Mono (т.е. не более одного значения), поэтому он хорошо работает с Mono<ServerResponse> - значение будет асинхронно разрешено в памяти, и в зависимости от результата мы вернем другой ответ или обработаем бизнес-исключения вручную.

В случае Flux (т.е. значений 0..N) ошибка может произойти в любой момент времени.

В этом случае вы можете использовать оператор collectList, чтобы превратить ваш Flux<String> в Mono<List<String>>, с большим предупреждением: все элементы будут помещены в буфер в памяти. Если поток данных важен или ваш контроллер / клиент полагается на потоковые данные, это не лучший выбор.

Боюсь, у меня нет лучшего решения этой проблемы, и вот почему: поскольку ошибка может произойти в любое время в течение Flux, нет гарантии, что мы сможем изменить статус HTTP и ответ: возможно, все уже было сброшено сеть. Это уже происходит при использовании Spring MVC и возвращении InputStream или Resource.

Функция обработки ошибок Spring Boot пытается записать страницу с ошибкой и изменить статус HTTP (см. ErrorWebExceptionHandler и реализацию классов), но если ответ уже зафиксирован, он будет регистрировать информацию об ошибке и сообщать вам, что статус HTTP, вероятно, был неправильным.

person Brian Clozel    schedule 12.02.2018
comment
Спасибо, отличные объяснения! Трудно полностью избавиться от старого синхронного мышления :) Чтобы решить свою проблему, я изменил свой сервисный API с дополнительными методами для выполнения этих проверок перед вызовом асинхронного вызова. - person Nicolas Barbé; 12.02.2018
comment
Я добавил несколько примеров в свой пост - person Hantsy; 17.05.2018

Я нашел другой способ решить эту проблему, перехватив исключение в методе body и сопоставив его с ResponseStatusException

public Mono<ServerResponse> myHandler(ServerRequest request) {
    return ok().contentType(MediaType.APPLICATION_JSON)
            .body( serviceReturningFluxError()
                    .onErrorMap(RuntimeException.class, e -> new ResponseStatusException( BAD_REQUEST, e.getMessage())), String.class);
}

При таком подходе Spring правильно обрабатывает ответ и возвращает ожидаемый код ошибки HTTP.

person Nicolas Barbé    schedule 10.06.2018