We're using org.springframework.web.reactive.function.client.WebClient
with
reactor.netty.http.client.HttpClient
as part of Spring 5.1.9 to make requests using the exchange()
method. The documentation for this method highlights the following:
... when using exchange(), it is the responsibility of the application to consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak.
Our use of exchange()
is rather basic, but the documentation for error scenarios is unclear to me and I want to be certain that we are correctly releasing resources for all outcomes. In essence, we have a blocking implementation which makes a request and returns the ResponseEntity
regardless of the response code:
try {
...
ClientResponse resp = client.method(method).uri(uri).syncBody(body).exchange().block();
ResponseEntity<String> entity = resp.toEntity(String.class).block();
return entity;
} catch (Exception e) {
// log error details, return internal server error
}
If I understand the implementation, exchange()
will always give us a response if the request was successfully dispatched, regardless of response code (e.g. 4xx, 5xx). In that scenario, we just need to invoke toEntity()
to consume the response. My concern is for error scenarios (e.g. no response, low-level connection errors, etc). Will the above exception handling catch all other scenarios and will any of them have a response that needs to be consumed?
Note: ClientResponse.releaseBody()
was only introduced in 5.2
While Initialising WebClient As mentioned in the code block, whenever a 5XX/4XX Error occurs, we can throw a user defined exception, and then execute error handling logic based on those user defined exceptions. Once this error Handler is defined, we can add it in the WebClient Initialisation.
Spring MVC Framework provides following ways to help us achieving robust exception handling. Controller Based - We can define exception handler methods in our controller classes. All we need is to annotate these methods with @ExceptionHandler annotation. This annotation takes Exception class as argument.
Spring Batch has built-in support, its skip and retry features, to handle errors when a job is executing. Skip and retry are about avoiding crashes, but crashes are inevitable, so Spring Batch also supports restarting a job after a failed execution.
There are three ways that we can use onErrorResume to handle errors: Compute a dynamic fallback value. Execute an alternative path with a fallback method. Catch, wrap and re-throw an error, e.g., as a custom business exception.
When using the retrieve () method of Spring WebClient in conjunction with bodyToMono default error handling is applied (if the response has status code 4xx or 5xx, the Mono will contain a WebClientException).
Once you start to use it, you will have various methods to put in use for sure. To handle errors in WebClient, you can use extend retrieve method. The retrieve method in WebClient throws an WebClientResponseException when there will be a 4xx and 5xx series exception received. You can further customize it using the onStatus () method, like below.
We can use onStatus (Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) method to handle or customize the exception. We recommend, go through the Spring Boot– Consuming a REST Services with WebClient article before proceeding. 1.
The retrieve () method in WebClient throws a WebClientResponseException whenever the API response with status code 4xx or 5xx is received. We can use onStatus (Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) method to handle or customize the exception.
The response have to be consumed when the request was made, but if you can't do the request probably an exception was be throwed before, and you will no have problems with response.
In the documentation says:
NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods:
- body(BodyExtractor)
- bodyToMono(Class) or bodyToMono(ParameterizedTypeReference)
- bodyToFlux(Class) or bodyToFlux(ParameterizedTypeReference)
- toEntity(Class) or toEntity(ParameterizedTypeReference)
- toEntityList(Class) or toEntityList(ParameterizedTypeReference)
- toBodilessEntity()
- releaseBody()
You can also use bodyToMono(Void.class) if no response content is expected. However keep in mind the connection will be closed, instead of being placed back in the pool, if any content does arrive. This is in contrast to releaseBody() which does consume the full body and releases any content received.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html
You can try to use .retrieve() instead .exchange() and handle errors as your preference.
public Mono<String> someMethod() {
return webClient.method(method)
.uri(uri)
.retrieve()
.onStatus(
(HttpStatus::isError), // or the code that you want
(it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
)
.bodyToMono(String.class);
}
private Mono<? extends Throwable> handleError(String message) {
log.error(message);
return Mono.error(Exception::new);
}
In this example I used Exception but you can create some exception more specific and then use some exception handler to return the http status that you want. Is not recommended to use block, a better way is pass the stream forward.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With