首页 > 解决方案 > 如何在将响应返回给调用者时注销对 Spring WebFlux WebClient 请求的失败响应的主体?

问题描述

我对反应式编程非常陌生,我有一个 REST 服务,它接受一个请求,然后使用 WebFlux WebClient 调用另一个 API。当 API 响应 4xx 或 5xx 响应时,我想在我的服务中记录响应正文,然后将响应传递给调用者。我找到了许多处理记录响应的方法,但它们通常会将 Mono.error 返回给调用者,这不是我想要做的。我几乎可以正常工作,但是当我向我的服务发出请求时,当我取回 API 返回的 4xx 代码时,我的客户端只是挂起等待响应的主体,并且服务似乎从未完成处理流. 我正在使用 Spring Boot 版本 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);
}

如您所见,我只是试图从 logResponseError 方法返回原始响应的 Mono。对于我的测试,我提交了一个包含错误元素的正文,这会导致来自我正在调用的 API 中的 ORDERS_URI 端点的 422 Unprocessable Entity 响应。但是由于某种原因,当调用 create-order 端点的客户端收到 422 时,它永远不会收到正文。如果我将 logResponseError 方法中的返回更改为

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

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

标签: javaspringspring-webfluxspring-webclient

解决方案


不能把你的蛋糕也吃掉!

这里的问题是您试图两次使用响应的主体,这是不允许的。通常你会得到一个错误这样做。

曾在

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


推荐阅读