vertx-web
vertx-web copied to clipboard
fail() in RoutingContextImpl constructor causes "Unhandled exception in router" even if error was handled
Version
Discovered in 4.3.8, but I can see it in master too.
Context
When a request path is empty or does not start with /, RoutingContext fails in its constructor:
if (path == null || path.length() == 0) {
// HTTP paths must start with a '/'
fail(400);
} else if (path.charAt(0) != '/') {
// For compatiblity we return `Not Found` when a path does not start with `/`
fail(404);
}
This will trigger failure handler assigned to this route immediately. However this constructor is called in RouterImpl like this:
new RoutingContextImpl(null, this, request, state.getRoutes()).next();
What this means that the order of actions is such:
- Routing context constructor
1. Call
fail()andfailed()now returns true 2. doFail() causesiterateNext3. Error handler is found 4. Error handler is executed - Constructor was completed
- Back in
RouterImpl,next()is called on constructedRoutingContextImpl - Because routing context iterated already, it cannot find the error handler
"Unhandled exception in router"appears in the log
Do you have a reproducer?
Try this:
final Router router = Router.router(vertx);
router.get("/*")
.handler(routingContext -> {
routingContext.response().setStatusCode(200);
routingContext.end("Hello world!");
})
.failureHandler(routingContext->{
int statusCode = routingContext.statusCode();
final var failure = routingContext.failure();
final var request = routingContext.request();
if(failure != null) {
if(failure instanceof HttpException) {
statusCode = ((HttpException)failure).getStatusCode();
LOGGER.info("Handling HttpException at path {} code {}", request.path(), statusCode);
}
else {
statusCode = 500;
LOGGER.warn("Handling unexpected exception at path {}", request.path(), statusCode);
}
}
else if(statusCode == -1) {
statusCode = 500;
LOGGER.warn("status code was -1 at path {}", request.path(), statusCode);
}
else {
LOGGER.info("Handling error code without exception at path {} code {}", request.path(), statusCode);
}
try {
routingContext.end("error message here")
.onFailure((e)->{
LOGGER.error("cannot send error for path {}", request.path());
e.printStackTrace();
})
.onSuccess((unused)->{
LOGGER.info("sent error for path {}", request.path());
});
}
catch(Exception justSoYouKnowItDoesNotThrow) {
LOGGER.error("cannot call end()", justSoYouKnowItDoesNotThrow);
}
});
return vertx
.createHttpServer(new HttpServerOptions().setHandle100ContinueAutomatically(true).setTcpKeepAlive(true))
.requestHandler(router)
.listen(port, host)
.onSuccess(res -> LOGGER.info("Listening on {}:{}", host, port))
.onFailure(
cause
-> LOGGER.warn("Cannot listen on {}:{} error: {}", host, port, cause.toString()));
I can see this in my log if I make a request:
INFO HttpServer Handling error code without exception at path blablabla code 404
ERROR RoutingContext Unhandled exception in router
INFO HttpServer sent error for path blablabla
To make the request, I just used raw python socket and sent this:
tcp_conn = socket.create_connection(("127.0.0.1", 8080))
request_str = (f"GET blablabla HTTP/1.1\r\n"
+ f"Host: 127.0.0.1\r\n"
+ f"Content-Length: 0\r\n"
+ "Connection: close\r\n"
+ "\r\n")
request_data = request_str.encode()
tcp_conn.send(request_data)