如何在向调用方返回响应时注销对 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,因为您实际上并没有进行任何过滤,只是根据响应执行操作
我是反应式编程的新手,我有一个 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,因为您实际上并没有进行任何过滤,只是根据响应执行操作