Spring Webflux:Webclient:出错了

adr*_*roy 7 spring reactor spring-webflux

我正在使用spring webflux的webclient,如下所示:

WebClient.create()
            .post()
            .uri(url)
            .syncBody(body)
            .accept(MediaType.APPLICATION_JSON)
            .headers(headers)
            .exchange()
            .flatMap(clientResponse -> clientResponse.bodyToMono(tClass));
Run Code Online (Sandbox Code Playgroud)

它运作良好.我现在想要处理来自我正在调用的Web服务的错误(Ex 500内部错误).通常我会在"stream"上添加一个doOnError,而isu是Throwable来测试状态代码,

但我的问题是我想获得webservice提供的正文,因为它为我提供了一条我想要使用的消息.

我希望无论发生什么事情都要做flatMap,并测试自己的状态代码以反序列化或不反复身体.

Oma*_*IDI 22

我更喜欢使用 ClientResponse 提供的方法来处理 http 错误和抛出异常:

WebClient.create()
         .post()
         .uri( url )
         .body( bodyObject == null ? null : BodyInserters.fromValue( bodyObject ) )
         .accept( MediaType.APPLICATION_JSON )
         .headers( headers )
         .exchange()
         .flatMap( clientResponse -> {
             //Error handling
             if ( clientResponse.statusCode().isError() ) { // or clientResponse.statusCode().value() >= 400
                 return clientResponse.createException().flatMap( Mono::error );
             }
             return clientResponse.bodyToMono( clazz )
         } )
         //You can do your checks: doOnError (..), onErrorReturn (..) ...
         ...
Run Code Online (Sandbox Code Playgroud)

实际上,它与 DefaultWebClient 的 DefaultResponseSpec 中用于处理错误的逻辑相同。DefaultResponseSpec 是 ResponseSpec 的一个实现,如果我们使用retrieve()而不是exchange(),我们就会拥有它。


Wes*_*Gun 16

我们没有onStatus()吗?

    public Mono<Void> cancel(SomeDTO requestDto) {
        return webClient.post().uri(SOME_URL)
                .body(fromObject(requestDto))
                .header("API_KEY", properties.getApiKey())
                .retrieve()
                .onStatus(HttpStatus::isError, response -> {
                    logTraceResponse(log, response);
                    return Mono.error(new IllegalStateException(
                            String.format("Failed! %s", requestDto.getCartId())
                    ));
                })
                .bodyToMono(Void.class)
                .timeout(timeout);
    }
Run Code Online (Sandbox Code Playgroud)

和:

    public static void logTraceResponse(Logger log, ClientResponse response) {
        if (log.isTraceEnabled()) {
            log.trace("Response status: {}", response.statusCode());
            log.trace("Response headers: {}", response.headers().asHttpHeaders());
            response.bodyToMono(String.class)
                    .publishOn(Schedulers.elastic())
                    .subscribe(body -> log.trace("Response body: {}", body));
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • onStatus 很酷,但它有一个夸克,那就是空的响应体会绕过 `response -&gt; { }` lambda。即永远不会返回 Mono.error。相反,将返回 null Mono。 (7认同)

Moh*_*ale 14

你也可以这样做

return webClient.getWebClient()
 .post()
 .uri("/api/Card")
 .body(BodyInserters.fromObject(cardObject))
 .exchange()
 .flatMap(clientResponse -> {
     if (clientResponse.statusCode().is5xxServerError()) {
        clientResponse.body((clientHttpResponse, context) -> {
           return clientHttpResponse.getBody();
        });
     return clientResponse.bodyToMono(String.class);
   }
   else
     return clientResponse.bodyToMono(String.class);
});
Run Code Online (Sandbox Code Playgroud)

阅读这篇文章以获取更多示例链接,当我遇到类似的错误处理问题时,我发现它很有帮助


小智 10

我通过这样做得到了错误正文:

webClient
...
.retrieve()    
.onStatus(HttpStatus::isError, response -> response.bodyToMono(String.class) // error body as String or other class
                                                   .flatMap(error -> Mono.error(new RuntimeException(error)))) // throw a functional exception
.bodyToMono(MyResponseType.class)
.block();
Run Code Online (Sandbox Code Playgroud)


Arj*_*sma 6

请注意,在撰写本文时,5xx 错误不再导致底层 Netty 层出现异常。见https://github.com/spring-projects/spring-framework/commit/b0ab84657b712aac59951420f4e9d696c3d84ba2


Ste*_*eve 6

利用我学到的这个关于“使用 Reactor 抛出异常的正确方法”的精彩答案,我能够将这个答案放在一起。它使用.onStatus.bodyToMono、 和.handle将错误响应正文映射到异常。

// create a chicken
webClient
    .post()
    .uri(urlService.getUrl(customer) + "/chickens")
    .contentType(MediaType.APPLICATION_JSON)
    .body(Mono.just(chickenCreateDto), ChickenCreateDto.class) // outbound request body
    .retrieve()
    .onStatus(HttpStatus::isError, clientResponse ->
        clientResponse.bodyToMono(ChickenCreateErrorDto.class)
            .handle((error, sink) -> 
                sink.error(new ChickenException(error))
            )
    )
    .bodyToMono(ChickenResponse.class)
    .subscribe(
            this::recordSuccessfulCreationOfChicken, // accepts ChickenResponse
            this::recordUnsuccessfulCreationOfChicken // accepts throwable (ChickenException)
    );
Run Code Online (Sandbox Code Playgroud)

  • 最新的 Spring 版本,它更改为 `HttpStatus` 更改为 `HttpStatusCode`,他们这样做是为了浪费我的 30 分钟‍♂️ (5认同)

Art*_*lan 5

我做这样的事情:

Mono<ClientResponse> responseMono = requestSpec.exchange()
            .doOnNext(response -> {
                HttpStatus httpStatus = response.statusCode();
                if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
                    throw new WebClientException(
                            "ClientResponse has erroneous status code: " + httpStatus.value() +
                                    " " + httpStatus.getReasonPhrase());
                }
            });
Run Code Online (Sandbox Code Playgroud)

然后:

responseMono.subscribe(v -> { }, ex -> processError(ex));
Run Code Online (Sandbox Code Playgroud)