I'm trying to handle the not found (404) by using custom failureHandler to get the response as a JSON format to the client
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
public class RestApiVerticle extends AbstractVerticle {
@Override
public void start(Future<Void> startFuture) throws Exception {
Router router = Router.router(vertx);
router.route().handler(ctx -> {
ctx.response()
.putHeader("Cache-Control", "no-store, no-cache");
ctx.next();
});
router.route("/api*")
.produces("application/json")
.consumes("application/json")
.failureHandler(ctx -> {
final JsonObject json = new JsonObject()
.put("timestamp", System.nanoTime())
.put("status", ctx.statusCode())
.put("error", HttpResponseStatus.valueOf(ctx.statusCode()).reasonPhrase())
.put("path", ctx.request().path());
final String message = ctx.get("message");
if(message != null) {
json.put("message", message);
}
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE,"application/json; charset=utf-8");
ctx.response().end(json.encodePrettily());
});
router.get("/api/greeting")
.produces("application/json")
.consumes("application/json")
.handler(ctx -> {
final JsonObject greeting = new JsonObject()
.put("greeting", "Hello!");
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE,"application/json; charset=utf-8");
ctx.response().end(greeting.encodePrettily());
});
HttpServer httpServer = vertx.createHttpServer();
httpServer
.requestHandler(router::accept)
.listen(9090, "localhost", listenHandler -> {
if (listenHandler.succeeded()) {
startFuture.complete();
}
if(listenHandler.failed()) {
startFuture.fail(listenHandler.cause());
}
});
}
}
If I run below command which is the ideal scenario
curl -X GET -H "Content-type: application/json" http://localhost:9090/api/greeting
I will get below result
{
"greeting": "Hello!"
}
But If I run below command which is not found resource
curl -X GET -H "Content-type: application/json" http://localhost:9090/api/blah
I will get below result
<html>
<body>
<h1>Resource not found</h1>
</body>
</html>
@EmadAlblueshi you missed the documentation about error handling: http://vertx.io/docs/vertx-web/java/#_error_handling
The failure handler is supposed to catch failures in the flow, in other words it is triggered when you call context.fail(...) or some exeption is thrown. The use case you're looking for should be handled the same way you handle caching with a handled at the end that catches all non handled requests and be default returns your 404 message.
So your router should have a last handler which body is exactly what you've done in your failureHandler. Alternatively you could leave the failure handler and make a handler at the beginning that checks if a request path is valid and if not do: ctx.fail(404).
@pmlopes I think the last handler is much better to me as shown below :
router.route().failureHandler(ctx -> {
final JsonObject error = new JsonObject()
.put("timestamp", System.nanoTime())
.put("exception", ctx.failure().getClass().getName())
.put("exceptionMessage", ctx.failure().getMessage())
.put("path", ctx.request().path());
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE,"application/json; charset=utf-8");
ctx.response().end(error.encode());
});
router.get("/api/greeting")
.produces("application/json")
.consumes("application/json")
.handler(ctx -> {
final JsonObject greeting = new JsonObject()
.put("greeting", "Hello!");
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE,"application/json; charset=utf-8");
ctx.response().end(greeting.encode());
});
router.route().handler(ctx -> {
ctx.fail(new RuntimeException("Not found"));
});
But I suggest if it possible to add an extra parameter to the fail method for the RoutingContext with something like
// Exception with status code
router.route().handler(ctx -> {
ctx.fail(new RuntimeException("Not found") , 404);
});
Thank you :+1:
Hi, alternatively you could create your own exception type with a error code and in your failure handle try to get the code from it, say for example:
public class NotFoundException extends RuntimeException {
public int getHttpErrorCode() {
return 404;
}
public NotFoundException(String message) {
super(message);
}
}
And now if you want to throw a not found just use the class:
ctx.fail(new NotFoundException("some error message"));
and in your failure handler just check what type of exception it is and do the proper handling...
@pmlopes Hi, here you are what I did to manage the exceptions globally for REST API
public class VerticleException extends RuntimeException {
private int statusCode;
public VerticleException() {
super();
statusCode = 500;
}
public VerticleException(int code) {
super();
statusCode = code;
}
public VerticleException(String message) {
super(message);
statusCode = 500;
}
public VerticleException(String message, Throwable cause) {
super(message, cause);
statusCode = 500;
}
public VerticleException(String message, int code) {
super(message);
statusCode = code;
}
public VerticleException(Throwable cause, int code) {
super(cause);
statusCode = code;
}
public VerticleException(String message, Throwable cause, int code) {
super(message, cause);
statusCode = code;
}
public int getStatusCode() {
return statusCode;
}
}
And in the failure handler
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.route()
.consumes("application/json")
.produces("application/json");
router.route().handler(ctx -> {
ctx.response()
.putHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate")
.putHeader("Pragma", "no-cache")
.putHeader("Expires", "0")
.putHeader("Content-Type", "application/json; charset=utf-8");
ctx.next();
});
router.route().failureHandler(ctx -> {
VerticleRuntimeException exception = (VerticleRuntimeException) ctx.failure();
final JsonObject error = new JsonObject()
.put("timestamp", System.nanoTime())
.put("status", exception.getStatusCode())
.put("error", HttpResponseStatus.valueOf(exception.getStatusCode()).reasonPhrase())
.put("path", ctx.normalisedPath())
.put("exception", exception.getClass().getName());
if(exception.getMessage() != null) {
error.put("message", exception.getMessage());
}
ctx.response().setStatusCode(exception.getStatusCode());
ctx.response().end(error.encode());
});
Finally, the usage is going to be something like
ctx.fail(new VerticleException(result.cause(), 500));
Thank you :+1:
Just a comment. It looks pretty close to https://github.com/wisdom-framework/wisdom/blob/master/core/wisdom-api/src/main/java/org/wisdom/api/exceptions/HttpException.java.
Jersey has something similar.
I think you should replace:
VerticleRuntimeException exception = (VerticleRuntimeException) ctx.failure();
with some guard like:
if (ctx.failure() instanceof VerticleRuntimeException) {
...
}
because you might get other exceptions than yours, say that some code throws some RuntimeException your cast will fail and throw a ClassCastException...
@cescoffier Hi, you are right and thank you for sharing :+1:
@pmlopes Done. It's very clear now :+1:
Hi, sorry for bumping such an old thread but there are some pretty common use cases I've been finding myself writing very often.
First : the HttpException, basically in every project I'm writing with Vert.x I finally need such a concept at some point. ctx.fail(new BadRequest("Validation failed for field ...")) (think of JSR-303 for validating request body for instance, or query parameters). Not sure it's Vert.x rol to provide such classes, but a generic way of handling such errors would definitely be nice :
public class JSONErrorHandler implements ErrorHandler {
@Override
public void handle(RoutingContext context, HttpException he) {
response.setStatusCode(he.status);
response.end(new JsonObject().put("error", he.reason).put("status", he.status));
}
@Override
public void handleUnexpected(RoutingContext context) {
// the failureHandler we already know
}
}
This would definitely be a good addition to Vert.x imho.
Second topic : the "fallback route" is a pretty common use-case I'm also writing quite often. Obviously, the default "404 page" returned as text/html is fine but cannot suit every use-case.
Adding router.route() at the end of the route declarations does the trick, but it's kinda bad for documentation purposes. (think of Swagger for instance). Maybe an alias on RoutingContext, like RoutingContext::fallbackHandler could be a little bit better.
Third thing (but that's almost another topic by itself) is content-negociation. RoutingContextprovides a getAcceptableContentType method which is very useful, but it falls down to the developper to use it manually. Especially for failureHandler :
public void handle(RoutingContext context) {
switch (context.getAcceptableContentType()) {
case "application/json":
jsonErrorHandler.handle(context);
default:
// ...
}
}
What could be really awesome :
router.route(...).failureHandler("application/json", new JsonErrorHandler());
router.route(...).failureHandler(new GenericFailureHandler());
Failure is the most common use-case I've found for content-type negotiation, but I guess this could be used elsewhere, too. (Maybe for DataObject marshalling ? idk).
Let me know if I should open a discussion somewhere. Thanks for your work @pmlopes
Hi @aesteve ,
I found some commons to handle errors in Vert.x and here you are what I did in my project
public class Failure extends RuntimeException {
private int code;
public Failure() {
super();
code = 500;
}
public Failure(String message) {
super(message);
code = 500;
}
public Failure(Throwable throwable) {
super(throwable);
code = 500;
}
public Failure(int failureCode) {
code = failureCode;
initCause(new RuntimeException());
}
public Failure(String message, Throwable throwable) {
super(message, throwable);
code = 500;
}
public Failure(String message, int failureCode) {
super(message);
code = failureCode;
}
public Failure(Throwable throwable, int failureCode) {
super(throwable);
code = failureCode;
}
public Failure(String message, Throwable throwable, int failureCode) {
super(message, throwable);
code = failureCode;
}
public int getCode() {
return code;
}
public static Failure failure(Throwable throwable) {
if (throwable instanceof Failure) {
return (Failure) throwable;
}
if(throwable.getMessage() == null) {
return new Failure("No message provided", throwable.getCause());
}
return new Failure(throwable.getMessage(), throwable.getCause());
}
}
Now I can use the Failure class to handle any failures in Vert.x ecosystem. The default code is 500 and you can use whatever you like
Vert.x web example
router.get("/resources").handler(ctx -> {
try {
// blah blah
} catch (Throwable t) {
// the default is 500
ctx.fail(Failure.failure(t));
}
});
Vert.x eventbus example
vertx.eventBus().consumer("io.vertx.consumer").handler(message -> {
try {
message.reply("ok");
} catch (Throwable t) {
// default code 500
final Failure failure = Failure.failure(t);
// or custom code
final Failure failure = new Failure(t, -1);
message.fail(failure.getCode(), failure.getMessage());
}
});
The good thing as shown above is handling failures in a generic way regardless of the context in Vert.x
I hope it helps
Thanks
There is HttpStatusException in Vert.x now, but it seems it is not handled by a "default" failure handler?
Most helpful comment
@pmlopes Hi, here you are what I did to manage the exceptions globally for REST API
And in the failure handler
Finally, the usage is going to be something like
Thank you :+1: