WebClient reactor.core.Exceptions$ErrorCallbackNotImplemented - java

So I have the following piece of code to do a GET to a remote machine:
webClient.get()
.uri(myUri)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
.subscribe(text -> {
LOG.info(text);
});
I get this exception, no problem, I'm expecting it, but it's really hard to find any documentation how to handle these errors:
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.net.UnknownHostException

To handle these exceptions you need to add the following, adapt it to your case (in my case if I get an unkownHostException I simply log a warning that the requested service is not present:
webClient.get()
.uri(myUri)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> {
if (e instanceof UnknownHostException) {
LOG.warn("Failed to get myStuff, desired service not present");
} else {
LOG.error("Failed to get myStuff");
}
return Mono.just("Encountered an exception");
})
.subscribe(text -> {
LOG.info(text);
});
You handle the error, and send something to the next step. I really wish there was a way to stop there and not pass anything down the pipe.

Related

Spring Boot web Client

I am having an issue with the web client.
I am trying to make a post request. The request is good. The thing is, if I add onStatus in order to handle http error codes, I am getting a NPE when calling bodyToMono. If I remove onStatus, I get the response.
we could take this as an example:
Employee createdEmployee = webClient.post()
.uri("/employees")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(empl), Employee.class)
.retrieve()
.onStatus(HttpStatus::isError, clientResponse -> {
if (clientResponse.statusCode() == HttpStatus.resolve(402)) {
return Mono.error(new Exception("402"));
}
log.error("Error endpoint with status code {}", clientResponse.statusCode());
if (clientResponse.statusCode() == HttpStatus.resolve(500)) {
return Mono.error(new Exception("500"));
}
if (clientResponse.statusCode() == HttpStatus.resolve(512)) {
return Mono.error(new Exception("512"));
}
return Mono.error(new Exception("Error while processing request"));
})
.bodyToMono(Employee.class);
I wan to handle 4xx and 5xx errors including their specific subtypes (404,402) 500, 512
Before bodyToMono() you can use onStatus()
.onStatus(httpStatus -> {
return httpStatus.is4xxClientError(); // handle 4xx or use .is5xxServerError() or .value()
}, clientResponse -> Mono.error(new RuntimeException("custom exception")))
Another option is to use ExchangeFilterFunction and apply it to your webclient
ExchangeFilterFunction responseErrorHandler =
ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
clientResponse.statusCode(); // handle any status manually
return Mono.just(clientResponse);
});
WebClient webClient = WebClient.builder()
.filter(responseErrorHandler)

Testing onErrorResume() Spring Webflux

I have a service layer using Spring Webflux and reactor and I am writing unit test for this. I was able to test the good response scenario but not sure how to test onErrorResume() using StepVerifier. Also please let me know if there is a better way of handling exceptions in my controller(e.g: using switchIfEmpty())
Here is my controller method
public Mono<SomeType> getInfo(Integer id) {
return webClient
.get()
.uri(uriBuilder -> uriBuilder.path())
.header("", "")
.header("", "")
.header("", "")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.retrieve()
.bodyToMono(POJO.class)
.onErrorResume(ex -> {
if (ex instanceof WebFaultException)
return Mono.error(ex);
return Mono.error(new WebFaultException(ex.getMessage(), "Error on API Call", HttpStatus.INTERNAL_SERVER_ERROR));
});
}
}
You can mock the webclient and use Mockito.doThrow when webclientMock.get() is called.
YourWebClient webclientMock = mock(YourWebClient.class);
doThrow(RuntimeException.class)
.when(webclientMock)
.get();
// Call your method here
Exception exception = assertThrows(RuntimeException.class, () -> {
YourController.getInfo(someIntValue);
});
// If you chose to raise WebFaultException, addittionaly assert that the return values ( message, status) are the one you expected
An alternate way to test your WebClient code without having to mock the WebClient class itself, as that can quickly become very messy, is to build your WebClient with an ExchangeFunction that returns whatever response or error you expect. I've found this to be a happy medium between mocking out the WebClient and spinning up Wiremock for unit tests.
#Test
void ourTest() {
ExchangeFunction exchangeFunction = mock(ExchangeFunction.class);
// this can be altered to return either happy or unhappy responses
given(exchangeFunction.exchange(any(ClientRequest.class))).willReturn(Mono.error(new RuntimeException()));
WebClient webClient = WebClient.builder()
.exchangeFunction(exchangeFunction)
.build();
// code under test - this should live in your service
Mono<String> mono = webClient.get()
.uri("http://someUrl.com")
.retrieve()
.bodyToMono(POJO.class)
.onErrorResume(ex -> {
if (ex instanceof WebFaultException)
return Mono.error(ex);
return Mono.error(new WebFaultException(ex.getMessage(), "Error on API Call", HttpStatus.INTERNAL_SERVER_ERROR));
});
StepVerifier.create(mono)
.expectError(RuntimeException.class)
.verify();
}

Spring reactor - How to properly throw Exception?

I have this code:
public void createImage(Image image) {
tokenProvider.getAccessToken()
.flatMap(accessToken -> restCllent.decodeColour(url, accessToken.getToken())
.flatMap(colour -> restClient.createImage(url, accessToken.getToken())))
.subscribe();
}
in the function decodeColour I have this code which call external service:
public Mono<Colour> decodeColour(String path, String token) {
log.info("Executing GET request to {}", path);
return webClient
.get()
.uri(path)
.header(HttpHeaders.AUTHORIZATION, String.format(TOKEN_BEARER, token))
.retrieve()
.bodyToMono(Colour.class)
.onErrorResume(e -> Mono.error(new RuntimeException("Error occurred during colour decoding: " + e.getMessage())));
}
When external service returns for example 401, I handled that in onErrorResume(),
and I want to just throw RuntimeException.
In the code above "createImage(Image image)" function, how I can force the code to just throw the RuntimeException if the error occurred in decodeColour()?
If I leave it like this, I get
ErrorCallbackNotImplemented: RuntimeException: Error occurred during colour decoding.
And if I add some error callback like doOnError, it just handle the error, but I want to program throws my RuntimeException. It is important to me to do that.

Spring-Webflux : Print Error Response and throw exception incase of error from web client

I am trying to log the error response that is coming from web client using onStatus or exchange.I am able to throw exception based on error code, but unable to log response.In Success scenario I can successfully log message.
Below is my Code
I tried with both .retreive method and also .exchange method.But instead of printing response coming from client following code is printing
Response Printing currently;
checkpoint("Body from POST *****Printing Backend Url ******[DefaultClientResponse]")
Expected Response:
{errorCd:404, errorMsg:"Not Found",errorDetails"Person Not Found for Given Request"}
The Above Message is being returned from client, when I use postman or soapui
Using .retrieve() Method
webClient
.post()
.uri(url)
.header(ACCEPT, APPLICATION_JSON_VALUE)
.bodyValue(request)
.retrieve()
.onStatus({ httpStatus -> HttpStatus.NOT_FOUND == httpStatus }, {
logger.info { "Client Response :${it.bodyToMono(String::class.java)}" }
;Mono.error(MyCustomException))
})
.bodyToMono(Person::class.java)
Using .exchange() Method
webClient
.post()
.uri(url)
.header(ACCEPT, APPLICATION_JSON_VALUE)
.body(BodyInserters.fromObject(request))
.exchange()
.flatMap {
clientResponse ->
if (clientResponse.statusCode().is4xxClientError) {
clientResponse.body { clientHttpResponse, _ -> clientHttpResponse.body}
logger.info { "Error Response:"+clientResponse.bodyToMono(String::class.java)}; Mono.error(MyCustomException()))
} else clientResponse.bodyToMono(Person::class.java)
}
AnyHelp Would be Appreciated.

How to handle error responses in a chain of CompletableFutures?

I have a long chain of completable futures in my project, with each step calling a backend API, which can give multiple error responses and one success response. Now, after parsing the response, I need to judge if it's an error, then I need to show to the user. I also need to know which stage in my chain, produced this error.
My approach right now (shown below) is to throw a Runtime Exception whenever I encounter an error response, and then append exceptionally block to my chain. I feel that this is not the best way to do it, since a runtime exception doesn't fit in this scenario. It also makes my code ugly, since I have to do it whenever I process a response, leading to an extra exception check. Is there a better way to do it?
CompletableFuture.supplyAsync(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
logger.error("this is error response");
throw new ResponseErrorException("Error response received for request");
}
})
This is basically repeated for every step in the chain.
Summary: If I get a failure response in any of the steps in a CompletableFuture chain, what's a good way to propagate it to the user?
Edit: If there's no better approach, please feel free to share your views on my approach.
My suggestion is using Decorator pattern for the responses. Suggest you have something like this
CompletableFuture
.supplyAsync(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
throw new ResponseErrorException("Error response received for request");
}
})
.thenApply(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
throw new ResponseErrorException("Another Error response received for request");
}
})
.exceptionally(ex -> "Error: " + ex.getMessage());
and if you would like to avoid duplication in throwing exceptions you could use following approach
CompletableFuture
.supplyAsync(() -> {
//some api call
Response response = ThrowExceptionOnErrorResponse(request.send());
})
.thenApply(() -> {
//some api call
Response response = ThrowExceptionOnErrorResponse(request.send());
}
})
.exceptionally(ex -> "Error: " + ex.getMessage());
class ThrowExceptionOnError implements Response {
Response originalResponse;
ThrowExceptionOnError(Response originalResp) {
if(response.hasError()) {
throw new ResponseErrorException("Another Error response received for request");
}
this.originalResponse = originalResponse;
}

Categories