Exception Handling for async WebClient request - spring-boot

Recently I have been working with WebClient. I am creating both a sync request and an async request. In a sync request, I am able to catch WebClientResponseException whereas when I try to catch WebClientResponseException (tried with generic Exception as well, didn't work) in an async request, it cannot be caught. I couldn't find any example on the internet. I am not sure if such exception handling is possible in async WebClient requests.
Here are the code examples:
Sync Request (Where the exception can be caught)
{
webClient
.post()
.uri(POST_ENDPOINT)
.bodyValue(cloudEvent)
.header("Authorization", token)
.retrieve()
.toEntity(String.class)
.block();
 
}
catch(final WebClientResponseException e)
{
log.error(String.valueOf(e));
throw new MyCustomException ("");
}
Async Request (Where the exception cannot be caught)
try
{
stream=
webClient
.get()
.uri(GET_ENDPOINT)
.header("Authorization", token)
.retrieve()
.bodyToFlux(Any_Parametrized_Type_Reference);
}
catch(ffinal WebClientResponseException e)
{
log.error(String.valueOf(e));
throw new MyCustomException ("");
}
Thanks in advance for any response.

Async means that the process will be transferd to another thread. bodyToFlux will do this and finish the execution, but you don´t have any control over that thread because, in your current thread, the function was executed and it doesn't trigger an issue.
This is similar to Promises in JS, where you should have a callback in case of any exception.
after bodyToFlux, you can add those callbacks.
webClient.get()
.uri("https://baeldung.com/path")
.retrieve()
.bodyToFlux(JsonNode.class)
.timeout(Duration.ofSeconds(5))
.onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("ReadTimeout"))
.onErrorReturn(SslHandshakeTimeoutException.class, new TextNode("SslHandshakeTimeout"))
.doOnError(WriteTimeoutException.class, ex -> log.error("WriteTimeout"))
onErrorMap, onErrorReturn and doOnError receive your callbacks and will execute the lambda methods in case of exception.
More info here: https://www.baeldung.com/spring-webflux-timeout#exception-handling

Thanks to #CarlosCárdenas; the following changes worked for me when I locally tested it.
private static final Function<WebClientRequestException, MyCustomException>
EXCEPTION_MAPPER =
ex ->
new MyCustomException(ex.getCause());
webClient
.get()
.uri(GET_ENDPOINT)
.header("Authorization", token)
.retrieve()
.bodyToFlux(Any_Parametrized_Type_Reference)
.onErrorMap(WebClientRequestException.class, EXCEPTION_MAPPER)
.doOnError(MyCustomException.class, ex -> myExceptionHandlingMethod(ex));
Currently, I have an issue with the related unit test. It doesn't throw an exception when it should, so I cannot catch the exception. I think I'm missing something about testing asynchronous flow. My test looks like this:
#Test
void myTest()
{
// Given
…
//When
final Executable executeMyMethod=
() -> myMethod(parameter1, parameter2);
// Then
// await().pollDelay(Duration.FIVE_SECONDS).untilAsserted(() -> assertTrue(true)); -> I’m not sure if it is needed or not
assertThrows(MyCustomException.class, executeMyMethod);
}

Related

Cannot test WebClient exception situation

I use Spring WebClient and write a test by following this page.
Everything is ok except from the exception test. I tried to use many different exception classes, but each time I get:
java.lang.AssertionError: Expecting a throwable with cause being an instance >of:java.lang.RuntimeException but current throwable has no cause.
The test method is here:
#Test
void throwsProductServiceExceptionWhenErrorStatus() throws JsonProcessingException {
server.enqueue(
new MockResponse()
.setResponseCode(500)
.setHeader("content-type", "application/json")
.setBody("{}"));
assertThatThrownBy(() -> client.findByName("flower"))
.hasCauseInstanceOf(RuntimeException.class);
// I also tried many other exception types e.g. JsonProcessingException
}
Here is my WebClient method that I want to create exception test:
private String fetchData(String title, String uri) {
// make GET call with WebClient.get and use a Mono of String to get the response as JSON string
return webClient
.get()
.uri(uri, title)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is5xxServerError,
error -> Mono.error(new RuntimeException(ERROR_WHEN_FETCHING_DATA)))
.bodyToMono(String.class)
.block();
}
Any idea?

How to correctly simulate latency with Spring WebClient

I want to add code that would simulate latency in my WebClient calls so I could ensure my timeouts/retries/etc are working correctly.
Since WebClient is reactive and uses a thread pool, it seems like Thread.sleep would block the thread in a way that WebClient wouldn't typically be blocked in real usage.
Is there a better way to simulate that latency?
(Inspired by https://github.com/fletchgqc/chaos-monkey-spring-boot/pull/2/files#diff-7f7c533cc2b344aa04848a17d0eff0cda404a5ab3cc55a47bba9ed019fba82e3R9
public class LatencyInducingRequestInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(
HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// do nothing
}
return response;
}
}
The answer is to use delayElement (The code I had posted above was for RestTemplate, that explains why Thread.sleep was used.
ExchangeFilterFunction latencyAddingFilterFunction =
(clientRequest, nextFilter) -> {
return nextFilter.exchange(clientRequest).delayElement(Duration.ofSeconds(2));
};

concurrent calls using spring webflux and hystrix

hoping someone can steer me in the right direction in turning my code into a more reactive service call. for background I have a preceding function that will generate a list of users, will then use that list to call this getUserDetails function for each user in the list, and return a map or list of user + details.
#HystrixCommand(commandKey = "getUserDetails")
public getUserResponse getUserDetails(userRequest request) {
getUserResponse response = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.onStatus(HttpStatus::isError, resp -> resp.bodyToMono(getUserError.class).map(errorHandler::mapRequestErrors))
.bodyToMono(getUserResponse.class).block();
return response;
}
Ideally I would also replace/remove the error mapping as only concerned with logging the returned error response and continuing.
so far I have thought something along the lines of this but I'm not sure the webflux/hystrix will play nice?
#HystrixCommand(commandKey = "getUserDetails", fallbackMethod = "getUserFallback")
public Mono<getUserResponse> getUserDetails(userRequest request) {
return = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.bodyToMono(getUserResponse.class);
}
#HystrixCommand
public Mono<getUserResponse> getUserFallback(userRequest request, Throwable throwable) {
log.error(//contents of error message returned)
return mono.empty();
}
public Flux<UserMap> fetchUserDetails(List<Integer> userIds) {
return Flux.fromIterable(userIds)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(userDetailsRepository::getUserDetails);
}
Hystrix is deprecated. If you have a chance, move to resilience4j which has support for Webflux/Reactor.
Spring also has dedicated support for resilience4j.
Regarding error handling you can leverage the rich set of operators from the Mono/Flux API like onErrorReturn or onErrorResume.

How to print responseBody with java reactor webclient before deserialization

We are planing to migrate from spring Rest-Template to Reactor-webclient.
With Rest-template we have written custom logging interceptors where we were printing request and response with uniqueId, before desrialization.
Now weblient provide filters, but in filters I can't access responseBody to log it.
We have some third party APIs where they send strings in case of error and some objects in case of success. In this case I can't wait to log response after deserialization, because it will break and we will not be able to log what response we got.
You can try creating a wrapper WebClient which will first log the response and then will deserialize.
The success response will fall on doOnSuccess and the error will fall on onErrorResume.
public <T> Mono<T> get(String url, Map<String, String> headersMap, Class<T> type) {
Mono<T> responseMono = client.get().uri(url).headers((h) -> headersMap.forEach(h::set)).retrieve()
.bodyToMono(type);
return responseMono.doOnSuccess(response -> log.debug("REST GET call to {} is successfull and response is {}",
url,response).onErrorResume(err -> {
log.error("Exception occurred calling get({}): {}", url,
err.getMessage());
return Mono.error(err);
});
}
Here is some sudo code for something I use to test with:
WebClient client = WebClient.create("www.google.com");
ObjectMapper mapper = new ObjectMapper();
client.get()
.retrieve()
.bodyToMono(String.class)
.map(rawBody -> {
try {
return mapper.readValue(rawBody, Response.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Cannot deserialize string: " + rawBody);
}
});

Global Exception handling in Spring reactor and dependent responses

I am trying to create a web application using spring 5 . It's a micro-service which hit few other micro-services. Response from one service is dependent the other.I am using global exception handing in my application.
Here is my code:
#Override
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.doOnSuccess(t -> {
// if success is returned.
// Want to return this innerResponse
Mono<Response> innerResponse =
httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
}).doOnError(t -> {
logger.info("Subscribing mono in Booking service - On Error");
Mono.error(new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT));
});
In case of error I want to throw CustomException and catch it in global exception handler:
#ControllerAdvice
public class ExceptionInterceptor {
public static Logger logger = Logger.getLogger(ExceptionInterceptor.class);
#ExceptionHandler(value = CustomException.class)
#ResponseBody
public Response authenticationFailure(ServerHttpRequest httpRequest, ServerHttpResponse response,
CustomException ex) {
logger.info("CustomException Occured with code => " + ex.getMessage());
return buildErrorResponse(ex.getMessage());
}
Based on the above code I have two problems:
The exception which is thrown in Mono.error() is not captured in global exception handler.
In case of success, response from the inner service should be returned.
Used two methods in mono: flatmap() and onErrorMap()
and updated my checkAvailablity() code:
public Mono<Response> checkAvailablity(Request request) {
Mono<Response> authResponse = userService.authenticateToken(request);
return authResponse.flatmap(t -> {
// Added transform() for success case
Mono<Response> response = httpService.sendRequest(Constant.SER_BOOKING_SERVICE_CHECK_AVAILABILTY,
request.toString(), Response.class);
logger.info("Response from SER_BOOKING_SERVICE_CHECK_AVAILABILTY");
return response;
}).onErrorMap(t -> {
// Added onErrorMap() for failure case & now exception is caught in global exception handler.
throw new CustomException(Constant.EX_MODULE_CONNECTION_TIMED_OUT);
});
}

Resources