java - 如何注销 Spring WebFlux WebClient 请求的失败响应正文,同时将响应返回给调用者?

标签 java spring spring-webflux spring-webclient

我对响应式(Reactive)编程非常陌生,我有一个 REST 服务,它接受请求,然后使用 WebFlux WebClient 调用另一个 API。当 API 以 4xx 或 5xx 响应进行响应时,我想在我的服务中记录响应正文,然后将响应传递给调用者。我找到了多种处理记录响应的方法,但它们通常将 Mono.error 返回给调用者,这不是我想要做的。我几乎可以正常工作,但是当我向我的服务发出请求时,当我取回 API 返回的 4xx 代码时,我的客户端只是挂起等待响应正文,并且服务似乎永远无法完成对流的处理。我正在使用 Spring Boot 版本 2.2.4.RELEASE。

这是我得到的:

Controller :

@PostMapping(path = "create-order")
public Mono<ResponseEntity<OrderResponse>> createOrder(@Valid @RequestBody CreateOrderRequest createOrderRequest) {
    return orderService.createOrder(createOrderRequest);
}

服务:

public Mono<ResponseEntity<OrderResponse>> createOrder(CreateOrderRequest createOrderRequest) {
    return this.webClient
            .mutate()
            .filter(OrderService.errorHandlingFilter(ORDERS_URI, createOrderRequest))
            .build()
            .post()
            .uri(ORDERS_URI)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(createOrderRequest)
            .exchange()
            .flatMap(response -> response.toEntity(OrderResponse.class));
}

public static ExchangeFilterFunction errorHandlingFilter(String uri, CreateOrderRequest request) {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        if (clientResponse.statusCode() != null && (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError())) {
            return clientResponse.bodyToMono(String.class)
                    .flatMap(errorBody -> OrderService.logResponseError(clientResponse, uri, request, errorBody));
        } else {
            return Mono.just(clientResponse);
        }
    });
}

static Mono<ClientResponse> logResponseError(ClientResponse response, String attemptedUri, CreateOrderRequest orderRequest, String responseBody) {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    try {
        log.error("Response code {} received when attempting to hit {}, request:{}, response:{}",
                response.rawStatusCode(), attemptedUri, objectMapper.writeValueAsString(orderRequest),
                responseBody);
    } catch (JsonProcessingException e) {
        log.error("Error attempting to serialize request object when reporting on error for request to {}, with code:{} and response:{}",
                attemptedUri, response.rawStatusCode(), responseBody);
    }
    return Mono.just(response);
}

如您所见,我只是尝试从 logResponseError 方法返回原始响应的 Mono。对于我的测试,我提交的正文包含错误元素,这会导致我调用的 API 中的 ORDERS_URI 端点发出 422 Unprocessable Entity 响应。但由于某种原因,虽然调用 create-order 端点的客户端收到 422,但它从未收到正文。如果我将 logResponseError 方法中的返回更改为

return Mono.error(new Exception("Some error"));

我在客户端收到 500,并且请求完成。如果有人知道为什么当我尝试发回响应本身时它无法完成,我很想知道我做错了什么。

最佳答案

鱼与熊掌不可兼得!

这里的问题是您试图使用响应正文两次,这是不允许的。通常,这样做会出现错误。

一旦进入

return clientResponse.bodyToMono(String.class) 

而且还在

response.toEntity(OrderResponse.class)

实际运行

@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
    return WebClientUtils.toEntity(this, bodyToMono(bodyType));
}

因此,一种解决方案是处理 ResponseEntity 而不是 ClientResponse,如下所示,因为您实际上不想对主体执行任何反应性操作

public Mono<ResponseEntity<OrderResponse>> createOrder(CreateOrderRequest createOrderRequest) {
    return this.webClient
            //no need for mutate unless you already have things specified in 
            //base webclient?
            .post()
            .uri(ORDERS_URI)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(createOrderRequest)
            .exchange()
            //Here you map the response to an entity first
            .flatMap(response -> response.toEntity(OrderResponse.class))
            //Then run the errorHandler to do whatever
            //Use doOnNext since there isn't any reason to return anything
            .doOnNext(response -> 
                errorHandler(ORDERS_URI,createOrderRequest,response));

}

//Void doesn't need to return
public static void  errorHandler(String uri, CreateOrderRequest request,ResponseEntity<?> response) {
    if( response.getStatusCode().is5xxServerError() 
        || response.getStatusCode().is4xxClientError())
            //run log method if 500 or 400
            OrderService.logResponseError(response, uri, request);
}

//No need for redundant final param as already in response
static void logResponseError(ResponseEntity<?> response, String attemptedUri, CreateOrderRequest orderRequest) {
    //Do the log stuff
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    try {
        log.error("Response code {} received when attempting to hit {}, request:{}, response:{}",
                response.getStatusCodeValue(), attemptedUri, objectMapper.writeValueAsString(orderRequest),
                response.getBody());
    } catch (JsonProcessingException e) {
        log.error("Error attempting to serialize request object when reporting on error for request to {}, with code:{} and response:{}",
                attemptedUri, response.getStatusCodeValue(), response.getBody());
    }
}

请注意,实际上没有理由使用 ExchangeFilter,因为您实际上并未执行任何过滤,只是根据响应执行操作

关于java - 如何注销 Spring WebFlux WebClient 请求的失败响应正文,同时将响应返回给调用者?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61626093/

相关文章:

java - Butterknife @generate 不工作{错误 :(23, 6) 错误:找不到符号类绑定(bind)}

JavaFx可观察实体无需更改实体本身

Spring security oauth2 和表单登录配置

spring-data - 使用 spring webflux-reactor 进行嵌套数据访问调用

spring-security - 如何创建一个数据类实现 Spring Security 特定的 UserDetails

java - 如何在 EmbeddedId 中强制执行 @Enumerated(EnumType.STRING)

java - 如何使用 Canvas 显示所有尺寸的图像

java - hibernate 拦截器对象内的数据但实体变量下的数据为空 - 无法保存到存储库

java - windows环境下删除文件失败

java - 使用 WebClient 时对 POST 请求正文进行单元测试