如何注销对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响应。但由于某种原因,当调用创建订单端点的客户端接收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,因为您实际上并没有进行任何筛选,只是根据响应执行操作

最新更新