Vertx - using the Router failureHandler for the errors in async calls - vert.x

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

Related

How to deal with HTTP connection timed out crashes in Flutter

So I have a method that uses the Flutter HTTP library and is responsible for calling HTTP requests to the server with code like this:
Future<List<DataModel>> fetchData() async {
try {
var url = Uri.parse('${baseUrlParse}myapipath');
var request = await http.get(url);
var data = jsonDecode(request.body);
return data;
} catch (e) {
print('Catch ${e}');
rethrow;
}
}
This code runs fine and has no issues.
It got to the point where when I have no internet connection or server connection fails, the app freezes, and an error file appears (if you're debugging in VS Code), called http_impl.dart, and the error snippet goes something like this:
onError: (error) {
// When there is a timeout, there is a race in which the connectionTask
// Future won't be completed with an error before the socketFuture here
// is completed with a TimeoutException by the onTimeout callback above.
// In this case, propagate a SocketException as specified by the
// HttpClient.connectionTimeout docs.
if (error is TimeoutException) {
assert(connectionTimeout != null);
_connecting--;
_socketTasks.remove(task);
task.cancel();
throw SocketException(
"HTTP connection timed out after $connectionTimeout, "
"host: $host, port: $port");
}
_socketTasks.remove(task);
_checkPending();
throw error;
});
I have tried to implement from this source and this, but when I make a request but have no connection, this error still occurs.
How to deal with this problem?
What I want is, if there is a problem with HTTP either there is no connection, or it fails to contact the server, then I can make a notification..
Is there something wrong with my code?
Please help, thank you
You re throw the exception in your code,
You need to catch exception where you call to this method like this.
try {
await fetchData();
} catch (e) {
// TODO: handle exception
}
You can stop VS Code catching unhandled exceptions from this way
https://superuser.com/a/1609472

Vert.X: Rare "Unhandled exception in router" despite errorHandler with statusCode 500

I use errorHandler to log interesting general information like User-ID (if available) and IP. This normally works, but rarely I see "Unhandled exception in router" that hasn't passed by my errorHandler:
ERROR io.vertx.ext.web.RoutingContext - Unhandled exception in router
My errorHandler:
router.errorHandler(500) { ctx ->
// Logging happens.
}
From documentation:
You can use to manage general errors too using status code 500. The handler will be called when the context fails and other failure handlers didn't write the reply or when an exception is thrown inside an handler.
What is missing?
An error handler is different from an exception/failure handler
router.route().failureHandler(this::handleFailure)
private fun handleFailure(routingContext: RoutingContext) {
routingContext.response()
.putHeader("Content-type", "application/json; charset=utf-8")
.setStatusCode(500)
.end(routingContext.failure().message ?: "internal server error")
}

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));
});
}
)

Failure handler issue using Vertx web routes and Minity in a Quarkus application

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)
);

Abort (HTTP 500) request when exception in request's handleEnd()

I'm using vert.x-web to implement a small service. One of my handlers for the end of the request (set via context.request().endHandler()) throws this NullPointerException:
2018-09-02 20:54:35,125 - ERROR [vert.x-eventloop-thread-1] (ContextImpl.java:345) - lambda$wrapTask$2()
Unhandled exception
java.lang.NullPointerException
at (My handler class)
at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:417)
at io.vertx.core.http.impl.Http1xServerConnection.handleEnd(Http1xServerConnection.java:482)
at io.vertx.core.http.impl.Http1xServerConnection.handleContent(Http1xServerConnection.java:477)
at io.vertx.core.http.impl.Http1xServerConnection.processMessage(Http1xServerConnection.java:458)
at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:144)
at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:712)
at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:619)
at io.vertx.core.net.impl.VertxHandler.lambda$channelRead$1(VertxHandler.java:146)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:337)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:195)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:144)
Why doesn't this exception call my requests's exception handler? Why is it unhandled? I have the request's exception handler set to context.fail() (via context.request().exceptionHandler()). But it does not seem to have any effect.
Is there another exception handler I'm unaware of?
Edit: here is the minimal reproducing code:
Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);
router.route().handler(context -> {
context.request()
.exceptionHandler(context::fail)
.endHandler(nothing -> { throw new NullPointerException("null"); })
.handler(buffer -> {});
});
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080);
Expected behavior: context.fail(e) gets called and the connection closes with HTTP ERROR 500.
Got behavior: context is not failed, connection "hangs".
The exceptionHandler applies to the HttpServerRequest object. The method is inherited from the ReadStream interface. This callback is invoked whenever a problem occurs in the Vert.x/Netty code handling HTTP requests, not user code.
If you want to execute some code before the actual request processing, I would suggest to register a route and invoke RoutingContext#next in the handler:
Router router = Router.router(vertx);
router.route("/somepath").handler(routingContext -> {
// Handler invoked first
// Execute pre-processing logic
// And then...
context.next();
});
router.route("/somepath").handler(routingContext -> {
// Handler invoked second
// Execute processing logic
});
Then any failure in pre-processing logic will be caught and managed normally by the router.
Two things need to be pointed out here:
The hang is caused by the response is not explicitly ended (see HttpServerResponse#end().
To handle exception happened during request handling, add failure handler at route level (see Route#failureHandler()). Handling exception on request will only caught exception when reading the stream.
For example:
Router router = Router.router(vertx);
router.route().failureHandler(handler -> handler.response().end());
router.route().handler(routingContext -> routingContext.request().endHandler(handler -> {
throw new NullPointerException("exception here!");
}));
vertx.createHttpServer().requestHandler(router::accept).listen(8085);