spring 使用webflux处理异常并返回正确的HTTP代码

tp5buhyn  于 2023-02-07  发布在  Spring
关注(0)|答案(3)|浏览(371)

我正在使用WebFlux的功能端点,我使用onErrorResume将服务层发送的异常转换为HTTP错误代码:

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()));
}

只要服务返回单声道,它就能正常工作。如果服务返回通量,我应该怎么做?

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

public Mono<ServerResponse> handler(ServerRequest request) {
    ???
}
    • 编辑**

我尝试了下面的方法,但不幸的是,它不起作用。Flux.错误不是由onErrorResume处理的,也不会传播到框架。当异常在http响应的序列化过程中被解箱时,Spring Boot异常管理会捕获它,并将其转换为500。

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

我对这种行为感到惊讶,这是一个错误吗?

rkttyhzu

rkttyhzu1#

我发现了另一种解决这个问题的方法,即在body方法中捕获异常并将其Map到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错误代码。

afdcj2ne

afdcj2ne2#

您的第一个示例使用Mono(即最多一个值),因此它很适合Mono<ServerResponse>-值将在内存中异步解析,根据结果,我们将返回不同的响应或手动处理业务异常。
Flux(即0..N值)的情况下,错误可能在任何给定时间发生。
在这种情况下,您可以使用collectList操作符将Flux<String>转换为Mono<List<String>>,并附带一个重要警告:所有元素都将在内存中缓冲。如果数据流很重要,或者如果您的控制器/客户端依赖于流数据,这不是这里的最佳选择。
恐怕我没有更好的解决方案,这个问题,这是为什么:由于在Flux期间的任何时间都可能发生错误,因此不能保证我们可以更改HTTP状态和响应:在使用Spring MVC并返回InputStreamResource时,已经出现了这种情况。
Sping Boot 错误处理功能尝试写入错误页面并更改HTTP状态(请参见ErrorWebExceptionHandler和实现类),但如果响应已经提交,它将记录错误信息并让您知道HTTP状态可能是错误的。

camsedfj

camsedfj3#

虽然这是一个老问题,我想回答它的任何人谁可能偶然发现这个堆栈溢出后。
还有另一种方法可以解决这个问题(下面将讨论),而不需要像其他答案中详述的那样在内存中缓存/缓冲所有元素。但是,下面显示的方法确实有一个局限性。首先,我将讨论方法,然后讨论局限性。

    • 方法**

你需要先把你的cold通量转换成hot通量。然后在热通量上调用.next(),返回Mono<Your Object>。在这个单声道上,调用.flatMap().switchIfEmpty().onErrorResume()。在flatMap()中,把返回的Your Object与热通量流连接起来。
下面是问题中发布的原始代码片段,经过修改以实现所需的功能:

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

public Mono<ServerResponse> handler(ServerRequest request)
{
    Flux<String> coldStrFlux = serviceReturningFluxError();

    // The following step is a very important step. It converts the cold flux 
    // into a hot flux.
    Flux<String> hotStrFlux = coldStrFlux.publish().refCount(1, Duration.ofSeconds(2));

    return hotStrFlux.next()
                     .flatMap( firstStr ->
                       {
                            Flux<String> reCombinedFlux = Mono.just(firstStr)
                                                .concatWith(hotStrFlux);
                            
                            return ServerResponse.ok()
                                          .contentType(MediaType.APPLICATION_JSON)
                                          .body(reCombinedFlux, String.class);
                        }
                    )
                    .switchIfEmpty(
                        ServerResponse.notFound().build()
                    )
                    .onErrorResume( throwable -> ServerResponse.badRequest().build() );
}

cold转换为hot Flux的原因是,这样做不会产生第二个冗余HTTP请求。
要获得更详细的答案,请参考下面的Stack Over帖子,在那里我对此进行了更详细的评论:Return relevant ServerResponse in case of Flux.error

限制

虽然上述方法适用于从服务返回的exceptions / Flux.error()流,但它不适用于在成功发出第一个元素后从通量发出各个元素时可能出现的任何异常。
上面代码中的假设很简单。如果服务抛出异常,那么从服务返回的第一个元素将是Flux.error()元素。这种方法没有考虑到异常可能在第一个元素之后在返回的通量流中抛出的事实。比如说可能是由于某些网络连接问题,该网络连接问题在前几个元素已经由通量流发射之后发生。

相关问题