在错误情况下从 WebClient 获取响应正文的正确方法是啥?

Posted

技术标签:

【中文标题】在错误情况下从 WebClient 获取响应正文的正确方法是啥?【英文标题】:What's the correct way to get the response body from a WebClient in an error case?在错误情况下从 WebClient 获取响应正文的正确方法是什么? 【发布时间】:2019-11-09 13:48:36 【问题描述】:

我是 WebClient 和响应式编程的新手。我想从请求中获取响应正文。如果发生错误,必须记录 http 代码、标头和正文,但仍应返回正文。

经过大量挖掘和谷歌搜索,我找到了两种解决方案。但在我看来,两者都过于复杂。有没有更简单的解决方案?

留在Mono 我找到了这个解决方案:

public Mono<String> log(ProtocolLine protocolLine) 
    return webClient.post()
            .uri("/log")
            .body(BodyInserters.fromObject(protocolLine))
            .exchange()
            .flatMap(clientResponse -> 
                Mono<String> stringMono = clientResponse.bodyToMono(String.class);
                CompletableFuture<String> stringCompleteFuture = new CompletableFuture<String>();
                Mono<String> bodyCompletedMono = Mono.fromFuture(stringCompleteFuture);
                if (clientResponse.statusCode().isError()) 
                    stringMono.subscribe(bodyString -> 
                        LOGGER.error("HttpStatusCode = ", clientResponse.statusCode());
                        LOGGER.error("HttpHeaders = ", clientResponse.headers().asHttpHeaders());
                        LOGGER.error("ResponseBody = ", bodyString);
                        stringCompleteFuture.complete(bodyString);
                    );
                

                return bodyCompletedMono;
            );

基于Flux,它需要更少的代码。但如果我知道只会有一个结果,我想我不应该使用 Flux。

public Flux<String> log(ProtocolLine protocolLine) 
    return webClient.post()
            .uri("/log")
            .body(BodyInserters.fromObject(protocolLine))
            .exchange()
            .flux()
            .flatMap(clientResponse -> 
                Flux<String> stringFlux = clientResponse.bodyToFlux(String.class).share();
                if (clientResponse.statusCode().isError()) 
                    stringFlux.subscribe(bodyString -> 
                        LOGGER.error("HttpStatusCode = ", clientResponse.statusCode());
                        LOGGER.error("HttpHeaders = ", clientResponse.headers().asHttpHeaders());
                        LOGGER.error("ResponseBody = ", bodyString);
                    );
                

                return stringFlux;
            );

【问题讨论】:

【参考方案1】:

这两种解决方案都是丑陋和错误的。您几乎不应该在反应式管道的中间订阅。订阅者通常是调用客户端,而不是您自己的应用程序。

    public Mono<String> log(ProtocolLine protocolLine) 
    return webClient.post()
            .uri("/log")
            .body(BodyInserters.fromObject(protocolLine))
            .exchange()
            .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)
                .doOnSuccess(body -> 
                    if (clientResponse.statusCode().isError()) 
                        log.error("HttpStatusCode = ", clientResponse.statusCode());
                        log.error("HttpHeaders = ", clientResponse.headers().asHttpHeaders());
                        log.error("ResponseBody = ", body);
                    
            ));

在这里你可以看到思维方式。我们总是使用我们的clientResponse 并将它的主体映射到一个字符串。然后,当订阅者(我们的调用客户端)使用此 Mono 时,我们 doOnSuccess 并检查状态代码是否有错误,如果是这种情况我们记录。

doOnSuccess 方法返回 void,因此它不会“消耗”单声道或任何东西,它只是在这个 Mono 说它“本身有东西”时触发一些东西,当它“完成”时,可以说。

这可以与Flux 以相同的方式使用。

【讨论】:

感谢您的回答。如果出现错误,我可以知道如何抛出异常。 @ThomasAndolf 如果使用retrieve() 而不是exchange(),你会怎么做?并且成功响应和错误响应包含不同的类型(成功返回SuccessResponse.class,错误返回ErrorResponse.class,都序列化成json)? 您使用onStatus 方法,如官方文档所示。 docs.spring.io/spring/docs/current/spring-framework-reference/… 然后你要求的是你想从同一个函数返回 2 种不同类型的对象。您不能从同一函数返回(例如)String 或 Int。如果您阅读反应器文档,您会返回一个单声道错误(包含 say en 异常或其他内容),然后在链中稍后会出现 .doOnError 在 Spring 5 中,exchange() 已被弃用:是否有另一种方法可以在发生错误时获取正文?使用retrieve() 或exchangeToMono(),我就是搞不定。 ExchangeToMono(response -> ...) 给你一个响应,你可以做任何你想做的事

以上是关于在错误情况下从 WebClient 获取响应正文的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 WebFlux WebClient 中测试状态码时如何获取响应正文?

WebClient - 如何获取请求正文?

如何记录 spring-webflux WebClient 请求 + 响应详细信息(正文、标头、elasped_time)?

在If case情况下从另一个中获取一个Activity

如何使用 WebClient 反应式 Web 客户端发送带有 zip 正文的 POST 请求

如何在没有 OutOfMemory 错误的情况下从 FileInputStream 获取字节数组