Vertx-web: RoutingContext failureHandler using custom exception with status code

Created on 27 Sep 2015  路  10Comments  路  Source: vert-x3/vertx-web

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>
invalid question

Most helpful comment

@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:

All 10 comments

@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:

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?

Was this page helpful?
0 / 5 - 0 ratings