Failure handler issue using Vertx web routes and Minity in a Quarkus application - vert.x

I have tried to use Munity API and Vertx Route together. The complete codes are here.
I defined a router rule like this.
router.put("/posts/:id").consumes("application/json").handler(BodyHandler.create()).handler(handlers::update);
And the handler implementation is like this.
LOGGER.log(Level.INFO, "\npath param id: {0}\nrequest body: {1}", new Object[]{id, body});
var form = fromJson(body, PostForm.class);
this.posts.findById(UUID.fromString(id))
.onFailure(PostNotFoundException.class).invoke(ex -> rc.response().setStatusCode(404).end())
.map(
post -> {
post.setTitle(form.getTitle());
post.setContent(form.getContent());
return this.posts.update(UUID.fromString(id), post);
}
)
.subscribe()
.with(data -> rc.response().setStatusCode(204).end());
In the findById method , it throws an PostNotFoundException.
public Uni<Post> findById(UUID id) {
return this.client
.preparedQuery("SELECT * FROM posts WHERE id=$1")
.execute(Tuple.of(id))
.map(RowSet::iterator)
// .map(it -> it.hasNext() ? rowToPost(it.next()) : null);
.flatMap(it -> it.hasNext() ? Uni.createFrom().item(rowToPost(it.next())) : Uni.createFrom().failure(PostNotFoundException::new));
}
When running the application, and do update(via HTTP PUT method) on the /posts/postid with a none existing post id, it will print a 404 error status as expected, but there is some pause time in the period.
In another handler method, it calls findById like the following and it works well and responds quickly when it is not found.
this.posts.findById(UUID.fromString(id))
.subscribe()
.with(
post -> rc.response().end(toJson(post)),
throwable -> rc.fail(404, throwable)
);

Related

Responding NACK on message in Quarkus reactive code cause the readiness check to fail

I am implementing a use case where I listen to messages coming from a Kafka topic in a quarkus application in a reactive manner. I followed as base code the KafkaDeadLetterTopic class from this Gist included on this blogpost https://quarkus.io/blog/kafka-failure-strategy/
When I alter the code to be reactive and return an Uni like this
#Incoming("movies")
public Uni<Void> consume1(IncomingKafkaRecord<String, String> movie) {
return Uni.createFrom().item(movie)
.onItem()
.transform(item -> {
if (movie.getPayload().contains(",")) {
throw new IllegalArgumentException(
"I don't like movie with , in their title: " + movie);
}
return item;
})
.onItem().invoke(() -> movie.ack())
.onFailure().invoke(throwable -> movie.nack(throwable))
.onItem()
.ignore().andContinueWithNull();
}
The messages are still being sent to configured Dead Letter Queue, but the health check is marked as unhealthy, making the application to be restarted by the container orchestrator.
Is this a bug? Am I using incorrectly the Uni on this case? Is the use of case of sending to DLQ from a reactive code supported?
I tried what Clement suggest and it works nicely. It seems that I wasn't correctly handling error recovery on Mutiny. This is my final code
#Incoming("movies")
public Uni<Void> consume1(IncomingKafkaRecord<String, String> movie) {
return Uni.createFrom().item(movie)
.onItem()
.transform(item -> {
if (movie.getPayload().contains(",")) {
throw new IllegalArgumentException(
"I don't like movie with , in their title: " + movie);
}
return item;
})
.onItem().invoke(() -> movie.ack())
.onItem().ignore().andContinueWithNull()
.onFailure().recoverWithUni(throwable -> Uni.createFrom().completionStage(movie.nack(throwable)));
}

how to merge the response of webClient call after calling 5 times and save the complete response in DB

i have scenario like:
i have to check the table if entry is available in DB then if available i need to call the same external api n times using webclient, collect all the response and save them in DB. if entry is not available in DB call the old flow.
here is my implementation. need suggestions to improve it. without for-each
public Mono<List<ResponseObject>> getdata(String id, Req obj) {
return isEntryInDB(id) //checking the entry in DB
.flatMap(
x -> {
final List<Mono<ResponseObject>> responseList = new ArrayList<>();
IntStream.range(0, obj.getQuantity()) // quantity decides how many times api call t happen
.forEach(
i -> {
Mono<ResponseObject> responseMono =
webClientCall(
id,
req.getType())
.map(
res ->
MapperForMappingDataToDesriedFormat(res));
responseList.add(responseMono);
});
return saveToDb(responseList);
})
.switchIfEmpty(oldFlow(id, req)); //if DB entry is not there take this existing flow.
need some suggestions to improve it without using foreach.
I would avoid using IntStream and rather use native operator to reactor called Flux in this case.
You can replace, InsStream.range with Flux.range. Something like this:
return isEntryPresent("123")
.flatMapMany(s -> Flux.range(0, obj.getQuantity())
.flatMap(this::callApi))
.collectList()
.flatMap(this::saveToDb)
.switchIfEmpty(Mono.defer(() ->oldFlow(id, req)));
private Mono<Object> saveToDb(List<String> stringList){
return Mono.just("done");
}
private Mono<String> callApi(int id) {
return Mono.just("iterating" + id);
}
private Mono<String> isEntryPresent(String id) {
return Mono.just("string");
}

In Spring WebClient used with block(), get body on error

I am using WebClient from Spring WebFlux to communicate with a REST API backend from a Spring client.
When this REST API backend throws an exception, it answers with a specific format (ErrorDTO) that I would like to collect from my client.
What I have tried to do is to make my client throw a GestionUtilisateurErrorException(ErreurDTO) containing this body once the server answers with a 5xx HTTP status code.
I have tried several options :
I/ onStatus
#Autowired
WebClient gestionUtilisateursRestClient;
gestionUtilisateursRestClient
.post()
.uri(profilUri)
.body(Mono.just(utilisateur), UtilisateurDTO.class)
.retrieve()
.onStatus(HttpStatus::is5xxServerError,
response -> {
ErreurDTO erreur = response.bodyToMono(ErreurDTO.class).block();
return Mono.error(new GestionUtilisateursErrorException(erreur));
}
)
.bodyToMono(Void.class)
.timeout(Duration.ofMillis(5000))
.block();
This method doesn't work because webclient doesn't allow me to call the block method in the onStatus. I am only able to get a Mono object and I can't go further from here.
It seems like "onStatus" method can't be used in a WebClient blocking method, which means I can throw a custom Exception, but I can't populate it with the data from the response body.
II/ ExchangeFilterFunction
#Bean
WebClient gestionUtilisateursRestClient() {
return WebClient.builder()
.baseUrl(gestionUtilisateursApiUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(ExchangeFilterFunction.ofResponseProcessor(this::gestionUtilisateursExceptionFilter))
.build();
}
private Mono<ClientResponse> gestionUtilisateursExceptionFilter(ClientResponse clientResponse) {
if(clientResponse.statusCode().isError()){
return clientResponse.bodyToMono(ErreurDTO.class)
.flatMap(erreurDto -> Mono.error(new GestionUtilisateursErrorException(
erreurDto
)));
}
return Mono.just(clientResponse);
}
This method works but throw a reactor.core.Exceptions$ReactiveException that I am struggling to catch properly (reactor.core.Exceptions is not catchable, and ReactiveException is private).
This Exception contains in its Cause the exception I need to catch (GestionUtilisateurErrorException) but I need a way to catch it properly.
I also tried to use "onErrorMap" and "onErrorResume" methods but none of them worked the way I needed.
Edit 1 :
I am now using the following workaround even if I feel it's a dirty way to do what I need :
gestionUtilisateursRestClient
.post()
.uri(profilUri)
.body(Mono.just(utilisateur), UtilisateurDTO.class)
.retrieve()
.onStatus(h -> h.is5xxServerError(),
response -> {
return response.bodyToMono(ErreurDTO.class).handle((erreur, handler) -> {
LOGGER.error(erreur.getMessage());
handler.error(new GestionUtilisateursErrorException(erreur));
});
}
)
.bodyToMono(String.class)
.timeout(Duration.ofMillis(5000))
.block();
}
catch(Exception e) {
LOGGER.debug("Erreur lors de l'appel vers l'API GestionUtilisateur (...)");
if(ExceptionUtils.getRootCause(e) instanceof GestionUtilisateursErrorException) {
throw((GestionUtilisateursErrorException) e.getCause());
}
else {
throw e;
}
}
Here, it throws the expected GestionUtilisateursErrorException that I can handle synchronously.
I might implement this in a global handler to avoid writing this code around each call to my API.
Thank you.
Kevin
I've encountered a similar case for accessing the response body that might be of use to you using the Mono.handle() method (see https://projectreactor.io/docs/core/release/api/index.html?reactor/core/publisher/Mono.html).
Here handler is a SynchronousSink (see https://projectreactor.io/docs/core/release/api/reactor/core/publisher/SynchronousSink.html) and can call at most next(T) one time, and either complete() or error().
In this case, I call 'handler.error()' with a new GestionUtilisateursErrorException constructed with the 'erreur'.
.onStatus(h -> h.is5xxServerError(),
response -> {
return response.bodyToMono(ErreurDTO.class).handle((erreur, handler) -> {
// Do something with erreur e.g.
log.error(erreur.getErrorMessage());
// Call handler.next() and either handler.error() or handler.complete()
handler.error(new GestionUtilisateursErrorException(erreur));
});
}
)

Vertx - using the Router failureHandler for the errors in async calls

We have recently discovered the failureHandler in the Vertx router
We thought it could help us get rid of all the repetitive try-catch blocks we have. But alas, it seems that the exceptions that are thrown inside the callbacks are not caught by the failureHandler.
Example: below, the failureHandler is called only for the 3rd case:
// Get the user details
router.get("/user").handler(ctx -> {
ctx.response().headers().add("content-type", "application/json");
// some async operation
userApiImpl.getUser(ctx, httpClient, asyncResult -> {
// ctx.response().setStatusCode(404).end(); //1
// throw new RuntimeException("sth happened"); //2
ctx.fail(404); //3
});
});
// ============================
// ERROR HANDLER
// ============================
router.get("/user").failureHandler(ctx -> {
LOG.info("Error handler is in the action.");
ctx.response().setStatusCode(ctx.statusCode()).end("Error occurred in method");
});
Is this as expected?
Can we somehow declare a global try-catch in a router for the exceptions occurring in the async context?
It is expected that sending a response manually with an error code does not trigger the failure handler.
It should be triggered if:
the route path matches
a handler throws an exception or ctx.fail() is invoked
Here's an example:
Route route1 = router.get("/somepath/path1/");
route1.handler(routingContext -> {
// Let's say this throws a RuntimeException
throw new RuntimeException("something happened!");
});
Route route2 = router.get("/somepath/path2");
route2.handler(routingContext -> {
// This one deliberately fails the request passing in the status code
// E.g. 403 - Forbidden
routingContext.fail(403);
});
// Define a failure handler
// This will get called for any failures in the above handlers
Route route3 = router.get("/somepath/*");
route3.failureHandler(failureRoutingContext -> {
int statusCode = failureRoutingContext.statusCode();
// Status code will be 500 for the RuntimeException or 403 for the other failure
HttpServerResponse response = failureRoutingContext.response();
response.setStatusCode(statusCode).end("Sorry! Not today");
});
See the error handling section of the Vert.x Web documentation

Rxjava2: Map exception into Single<Boolean>

I'm trying to write a method the returns Single<Boolean> and it's functionality is to check whether an HTTP server is alive.
so it sends a ping to server and it works fine in case server is online, but in case server is down it returns an exception which is propagated to subscribe.
I would like to catch the exception and map it into false and return internally from isAlive method.
Here is my attempt:
public Single<Boolean> isAlive()
{
return client().get(HEALTH_CHECK_PATH) //
.rxSend() //
.onErrorReturnItem(null) //
.map(response -> {
return response.statusCode() == HttpResponse.HTTP_STATUS_NO_CONTENT;
});
}
I tried to return null and on the map check for null and return a boolean accordingly - map Single doesn't handle null....any idea?
Thanks.
EDIT-1
I've tried:
.onErrorResumeNext(err -> Single.just(Boolean.FALSE))
it doesn't compile..
Solved by doing:
public Single<Boolean> isAlive()
{
return client().get(HEALTH_CHECK_PATH) //
.rxSend() //
.map(response -> response.statusCode() == HttpResponse.HTTP_STATUS_NO_CONTENT)//
.onErrorResumeNext(err -> Single.just(Boolean.FALSE));
}