如何在向调用方返回响应时注销对 Spring WebFlux WebClient 请求的失败响应正文?

How do I log out the body of a failed response to a Spring WebFlux WebClient request while returning the response to the caller?

我是反应式编程的新手,我有一个 REST 服务,它接受一个请求,然后使用 WebFlux WebClient 调用另一个 API。当 API 以 4xx 或 5xx 响应响应时,我想在我的服务中记录响应主体,然后将响应传递给调用者。我找到了很多方法来处理记录响应,但它们通常 return Mono.error 给调用者,这不是我想要做的。我几乎可以正常工作了,但是当我向我的服务发出请求时,当我取回 API returned 的 4xx 代码时,我的客户端只是挂起等待响应的主体,并且该服务似乎永远不会完成对流的处理。我正在使用 Spring 启动版本 2.2.4.RELEASE.

这是我得到的:

控制器:

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

如您所见,我只是想 return 来自 logResponseError 方法的原始响应的单声道。对于我的测试,我提交了一个包含错误元素的正文,这导致我正在调用的 API 中的 ORDERS_URI 端点发出 422 Unprocessable Entity 响应。但出于某种原因,虽然调用创建订单端点的客户端收到 422,但它从未收到正文。如果我将 logResponseError 方法中的 return 更改为

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,因为您实际上并没有进行任何过滤,只是根据响应执行操作