Java 在 spring-webflux 中处理错误的正确方法是什么

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43575538/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:01:59  来源:igfitidea点击:

what is the right way to handle errors in spring-webflux

javaspring-webflux

提问by Juan Medina

I've been doing some research using spring-webflux and I like to understand what should be the right way to handle errors using Router Functions.

我一直在使用 spring-webflux 进行一些研究,我想了解使用路由器功能处理错误的正确方法应该是什么。

I've created an small project to test a couple of scenarios, and I like to get feedback about it, and see what other people is doing.

我创建了一个小项目来测试几个场景,我喜欢得到关于它的反馈,看看其他人在做什么。

So far what I doing is.

到目前为止,我所做的是。

Giving the following routing function:

给出以下路由功能:

@Component
public class HelloRouter {
    @Bean
    RouterFunction<?> helloRouterFunction() {
        HelloHandler handler = new HelloHandler();
        ErrorHandler error = new ErrorHandler();

        return nest(path("/hello"),
                nest(accept(APPLICATION_JSON),
                        route(GET("/"), handler::defaultHello)
                                .andRoute(POST("/"), handler::postHello)
                                .andRoute(GET("/{name}"), handler::getHello)
                )).andOther(route(RequestPredicates.all(), error::notFound));
    }
}

I've do this on my handler

我已经在我的处理程序上这样做了

class HelloHandler {

    private ErrorHandler error;

    private static final String DEFAULT_VALUE = "world";

    HelloHandler() {
        error = new ErrorHandler();
    }

    private Mono<ServerResponse> getResponse(String value) {
        if (value.equals("")) {
            return Mono.error(new InvalidParametersException("bad parameters"));
        }
        return ServerResponse.ok().body(Mono.just(new HelloResponse(value)), HelloResponse.class);
    }

    Mono<ServerResponse> defaultHello(ServerRequest request) {
        return getResponse(DEFAULT_VALUE);
    }

    Mono<ServerResponse> getHello(ServerRequest request) {
        return getResponse(request.pathVariable("name"));
    }

    Mono<ServerResponse> postHello(ServerRequest request) {
        return request.bodyToMono(HelloRequest.class).flatMap(helloRequest -> getResponse(helloRequest.getName()))
                .onErrorResume(error::badRequest);
    }
}

Them my error handler do:

他们我的错误处理程序做:

class ErrorHandler {

    private static Logger logger = LoggerFactory.getLogger(ErrorHandler.class);

    private static BiFunction<HttpStatus,String,Mono<ServerResponse>> response =
    (status,value)-> ServerResponse.status(status).body(Mono.just(new ErrorResponse(value)),
            ErrorResponse.class);

    Mono<ServerResponse> notFound(ServerRequest request){
        return response.apply(HttpStatus.NOT_FOUND, "not found");
    }

    Mono<ServerResponse> badRequest(Throwable error){
        logger.error("error raised", error);
        return response.apply(HttpStatus.BAD_REQUEST, error.getMessage());
    }
}

Here is the full sample repo:

这是完整的示例回购:

https://github.com/LearningByExample/reactive-ms-example

https://github.com/LearningByExample/reactive-ms-example

采纳答案by punkboyee

Spring 5 provides a WebHandler, and in the JavaDoc, there's the line:

Spring 5 提供了一个WebHandler,在 JavaDoc 中,有一行:

Use HttpWebHandlerAdapter to adapt a WebHandler to an HttpHandler. The WebHttpHandlerBuilder provides a convenient way to do that while also optionally configuring one or more filters and/or exception handlers.

使用 HttpWebHandlerAdapter 将 WebHandler 适配到 HttpHandler。WebHttpHandlerBuilder 提供了一种方便的方法来做到这一点,同时还可以选择配置一个或多个过滤器和/或异常处理程序。

Currently, the official documentation suggests that we should wrap the router function into an HttpHandler before booting up any server:

目前,官方文档建议我们应该在启动任何服务器之前将路由器功能包装到 HttpHandler 中:

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);

With the help of WebHttpHandlerBuilder, we can configure custom exception handlers:

WebHttpHandlerBuilder的帮助下,我们可以配置自定义异常处理程序:

HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
  .prependExceptionHandler((serverWebExchange, exception) -> {

      /* custom handling goes here */
      return null;

  }).build();

回答by Alphaone

Why not do it the old fashioned way by throwing exceptions from handler functions and implementing your own WebExceptionHandler to catch 'em all:

为什么不通过从处理程序函数中抛出异常并实现您自己的 WebExceptionHandler 来捕获所有异常来使用老式方法:

@Component
class ExceptionHandler : WebExceptionHandler {
    override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
        /* Handle different exceptions here */
        when(ex!!) {
            is NoSuchElementException -> exchange!!.response.statusCode = HttpStatus.NOT_FOUND
            is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
        }

        /* Do common thing like logging etc... */

        return Mono.empty()
    }
}

Above example is in Kotlin, since I just copy pasted it from a project I′m currently working on, and since the original question was not tagged for java anyway.

上面的例子是在 Kotlin 中,因为我只是从我目前正在处理的项目中复制粘贴它,并且因为原始问题无论如何都没有标记为 java。

回答by adrien le roy

What I am currently doing is simply providing a bean my WebExceptionHandler :

我目前正在做的只是提供一个 bean 我的 WebExceptionHandler :

@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
    return new MyWebExceptionHandler();
}

The advantage than creating the HttpHandlermyself is that I have a better integration with WebFluxConfigurerif I provide my own ServerCodecConfigurerfor example or using SpringSecurity

比创建HttpHandler自己的优势在于,WebFluxConfigurer如果我提供自己ServerCodecConfigurer的示例或使用,我可以更好地集成SpringSecurity

回答by Frischling

If you think, router functions are not the right place to handle exceptions, you throw HTTP Exceptions, that will result in the correct HTTP Error codes. For Spring-Boot (also webflux) this is:

如果您认为路由器功能不是处理异常的正确位置,您可以抛出 HTTP 异常,这将导致正确的 HTTP 错误代码。对于 Spring-Boot(也是 webflux),这是:

  import org.springframework.http.HttpStatus;
  import org.springframework.web.server.ResponseStatusException;
  .
  .
  . 

  new ResponseStatusException(HttpStatus.NOT_FOUND,  "Collection not found");})

spring securities AccessDeniedException will be handled correctly, too (403/401 response codes).

spring 证券 AccessDeniedException 也将被正确处理(403/401 响应代码)。

If you have a microservice, and want to use REST for it, this can be a good option, since those http exceptions are quite close to business logic, and should be placed near the business logic in this case. And since in a microservice you shouldn't have to much businesslogic and exceptions, it shouldn't clutter your code, too... (but of course, it all depends).

如果您有一个微服务,并希望对其使用 REST,这可能是一个不错的选择,因为那些 http 异常非常接近业务逻辑,在这种情况下应该放在业务逻辑附近。因为在微服务中你不应该有太多的业务逻辑和异常,它也不应该使你的代码混乱......(当然,这一切都取决于)。

回答by Hartmut

A quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException/ or create your own subclasses...

将异常映射到 http 响应状态的一种快速方法是抛出org.springframework.web.server.ResponseStatusException/或创建自己的子类...

Full control over http response status + spring will add a response body with the option to add a reason.

完全控制 http 响应状态 + spring 将添加一个响应主体,并可以选择添加reason.

In Kotlin it could look as simple as

在 Kotlin 中,它看起来很简单

@Component
class MyHandler(private val myRepository: MyRepository) {

    fun getById(req: ServerRequest) = req.pathVariable("id").toMono()
            .map { id -> uuidFromString(id) }  // throws ResponseStatusException
            .flatMap { id -> noteRepository.findById(id) }
            .flatMap { entity -> ok().json().body(entity.toMono()) }
            .switchIfEmpty(notFound().build())  // produces 404 if not found

}

fun uuidFromString(id: String?) = try { UUID.fromString(id) } catch (e: Throwable) { throw BadRequestStatusException(e.localizedMessage) }

class BadRequestStatusException(reason: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, reason)

Response Body:

响应机构:

{
    "timestamp": 1529138182607,
    "path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
    "status": 400,
    "error": "Bad Request",
    "message": "For input string: \"f7b.491bc\""
}

回答by Akhil Bojedla

You can write a Global exception handler with custom response data and response code as follows. The code is in Kotlin. But you can convert it to java easily:

您可以使用自定义响应数据和响应代码编写一个全局异常处理程序,如下所示。代码在 Kotlin 中。但是您可以轻松地将其转换为 java:

@Component
@Order(-2)
class GlobalWebExceptionHandler(
  private val objectMapper: ObjectMapper
) : ErrorWebExceptionHandler {

  override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {

    val response = when (ex) {
      // buildIOExceptionMessage should build relevant exception message as a serialisable object
      is IOException -> buildIOExceptionMessage(ex)
      else -> buildExceptionMessage(ex)
    }

    // Or you can also set them inside while conditions
    exchange.response.headers.contentType = MediaType.APPLICATION_PROBLEM_JSON
    exchange.response.statusCode = HttpStatus.valueOf(response.status)
    val bytes = objectMapper.writeValueAsBytes(response)
    val buffer = exchange.response.bufferFactory().wrap(bytes)
    return exchange.response.writeWith(Mono.just(buffer))
  }
}