英文:
Spring Reactive Web - Exceptions are always wrapped in 500
问题
使用一个非常简单的项目,错误处理并不清晰。
public class WebfluxApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebfluxApplication.class, args);
    }
}
public class EndpointRouter {
    @Bean
    public WebClient webClient() {
        return WebClient.builder().build();
    }
    @Bean
    public RouterFunction<ServerResponse> goodRoute(Handler handler, ConfigProperties configProperties) {
        log.info(configProperties.toString());
        return RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/ok"),
                handler::goodEndpoint)
                .and(
                        RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/notfound"), 
                            handler::badEndpoint));
    }
}
public Mono<ServerResponse> goodEndpoint(ServerRequest r) {
    return ServerResponse.ok().build();
}
public Mono<ServerResponse> badEndpoint(ServerRequest r) {
    var result = service.badEndpoint();
    return ServerResponse
            .ok()
            .body(result, String.class);
}
public class Service {
    private final WebClient webClient;
    private final ConfigProperties configProperties;
    public Service(WebClient webClient, ConfigProperties configurationProperties) {
        this.webClient = webClient;
        this.configProperties = configurationProperties;
    }
    public Mono<String> badEndpoint() {
        return webClient
                .get()
                .uri(configProperties.getNotfound())
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
                    if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
                        return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                                "Entity not found."));
                    } else {
                        return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
                    }
                })
                .bodyToMono(String.class);
    }
}
从阅读文档来看,我不应该为整个项目设置全局错误处理程序,我应该能够处理 404 错误,并将 404 返回给原始调用方。
这是输出:
2020-08-29 16:52:46.301 ERROR 25020 --- [ctor-http-nio-4] a.w.r.e.AbstractErrorWebExceptionHandler : [b339763e-1]  500 Server Error for HTTP GET "/api/v1/integration/notfound"
org.springframework.web.client.HttpClientErrorException: 404 Entity not found.
    at com.stevenpg.restperformance.webflux.Service.lambda$badEndpoint$0(Service.java:30) ~[main/:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ 404 from GET https://httpbin.org/status/404 [DefaultWebClient]
    |_ checkpoint ⇢ Handler com.stevenpg.restperformance.webflux.EndpointRouter$$Lambda$445/0x00000008003ae040@7799b58 [DispatcherHandler]
    |_ checkpoint ⇢ HTTP GET "/api/v1/integration/notfound" [ExceptionHandlingWebHandler]
我还尝试过在服务的 Mono 上使用 onErrorResume,但它从不正确工作,需要返回 Mono<ServerResponse> 而不是 Mono<String>。
文档和 Stack Overflow 上没有太多/任何关于在 RouterFunction 中进行 WebClient 调用并处理不同类型响应的示例。
英文:
With a very simple project, error handling is unclear.
public class WebfluxApplication {
    public static void main(String[] args) {
	    SpringApplication.run(WebfluxApplication.class, args);
    }
}
-
public class EndpointRouter
{
@Bean
public WebClient webClient() {
    return WebClient.builder().build();
}
@Bean
public RouterFunction<ServerResponse> goodRoute(Handler handler, ConfigProperties configProperties) {
    log.info(configProperties.toString());
    return
            RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/ok"),
                    handler::goodEndpoint)
    .and(
            RouterFunctions.route(RequestPredicates.GET("/api/v1/integration/notfound") {
                    handler::badEndpoint));
}
-
public Mono<ServerResponse> goodEndpoint(ServerRequest r) {
    return ServerResponse.ok().build();
}
public Mono<ServerResponse> badEndpoint(ServerRequest r) {
    var result = service.badEndpoint();
    return ServerResponse
            .ok()
            .body(result, String.class);
}
-
public class Service
{
private final WebClient webClient;
private final ConfigProperties configProperties;
public Service(WebClient webClient, ConfigProperties configurationProperties) {
    this.webClient = webClient;
    this.configProperties = configurationProperties;
}
public Mono<String> badEndpoint() {
    return webClient
            .get()
            .uri(configProperties.getNotfound())
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
                if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
                    return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                            "Entity not found."));
                } else {
                    return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
                }
            })
            .bodyToMono(String.class);
}
From reading the docs, I shouldn't need to set up a Global error handler for the entire project, I should be able to handle the 404, and return a 404 back to the original caller.
This is the output
   2020-08-29 16:52:46.301 ERROR 25020 --- [ctor-http-nio-4] a.w.r.e.AbstractErrorWebExceptionHandler : [b339763e-1]  500 Server Error for HTTP GET "/api/v1/integration/notfound"
org.springframework.web.client.HttpClientErrorException: 404 Entity not found.
	at com.stevenpg.restperformance.webflux.Service.lambda$badEndpoint$0(Service.java:30) ~[main/:na]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ 404 from GET https://httpbin.org/status/404 [DefaultWebClient]
	|_ checkpoint ⇢ Handler com.stevenpg.restperformance.webflux.EndpointRouter$$Lambda$445/0x00000008003ae040@7799b58 [DispatcherHandler]
	|_ checkpoint ⇢ HTTP GET "/api/v1/integration/notfound" [ExceptionHandlingWebHandler]
I've also tried using onErrorResume on my Mono from the service, but it never works correct and requires a return of Mono<ServerResponse> rather than Mono<String>.
The documentation and Stack Overflow don't have many/any examples of making a WebClient call inside a RouterFunction and handling different types of responses.
答案1
得分: 2
只需添加onErrorResume即可解决您的问题。否则错误将在AbstractErrorWebExceptionHandler中处理。
return webClient
    .get()
    .uri(configProperties.getNotfound())
    .retrieve()
    .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
        if (clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)) {
            return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                    "Entity not found."));
        } else {
            return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
        }
    })
    .bodyToMono(String.class)
    .onErrorResume(e -> Mono.just(e.getMessage()));
英文:
Just adding onErrorResume solves your problem. Otherwise error is handled in AbstractErrorWebExceptionHandler.
 return webClient
        .get()
        .uri(configProperties.getNotfound())
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, clientResponse -> {
            if(clientResponse.statusCode().equals(HttpStatus.NOT_FOUND)){
                return Mono.error(new HttpClientErrorException(HttpStatus.NOT_FOUND,
                        "Entity not found."));
            } else {
                return Mono.error(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
            }
        })
        .bodyToMono(String.class)
        .onErrorResume( e -> Mono.just(e.getMessage()) );
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论