The HTTP server verticle

The verticle class preamble and start method look as follows:

  1. public class HttpServerVerticle extends AbstractVerticle {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerVerticle.class);
  3. public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; (1)
  4. public static final String CONFIG_WIKIDB_QUEUE = "wikidb.queue";
  5. private String wikiDbQueue = "wikidb.queue";
  6. private FreeMarkerTemplateEngine templateEngine;
  7. @Override
  8. public void start(Promise<Void> promise) throws Exception {
  9. wikiDbQueue = config().getString(CONFIG_WIKIDB_QUEUE, "wikidb.queue"); (2)
  10. HttpServer server = vertx.createHttpServer();
  11. Router router = Router.router(vertx);
  12. router.get("/").handler(this::indexHandler);
  13. router.get("/wiki/:page").handler(this::pageRenderingHandler);
  14. router.post().handler(BodyHandler.create());
  15. router.post("/save").handler(this::pageUpdateHandler);
  16. router.post("/create").handler(this::pageCreateHandler);
  17. router.post("/delete").handler(this::pageDeletionHandler);
  18. templateEngine = FreeMarkerTemplateEngine.create(vertx);
  19. int portNumber = config().getInteger(CONFIG_HTTP_SERVER_PORT, 8080); (3)
  20. server
  21. .requestHandler(router)
  22. .listen(portNumber, ar -> {
  23. if (ar.succeeded()) {
  24. LOGGER.info("HTTP server running on port " + portNumber);
  25. promise.complete();
  26. } else {
  27. LOGGER.error("Could not start a HTTP server", ar.cause());
  28. promise.fail(ar.cause());
  29. }
  30. });
  31. }
  32. // (...)
  1. We expose public constants for the verticle configuration parameters: the HTTP port number and the name of the event bus destination to post messages to the database verticle.

  2. The AbstractVerticle#config() method allows accessing the verticle configuration that has been provided. The second parameter is a default value in case no specific value was given.

  3. Configuration values can not just be String objects but also integers, boolean values, complex JSON data, etc.

The rest of the class is mostly an extract of the HTTP-only code, with what was previously database code being replaced with event bus messages. Here is the indexHandler method code:

  1. private void indexHandler(RoutingContext context) {
  2. DeliveryOptions options = new DeliveryOptions().addHeader("action", "all-pages"); (2)
  3. vertx.eventBus().request(wikiDbQueue, new JsonObject(), options, reply -> { (1)
  4. if (reply.succeeded()) {
  5. JsonObject body = (JsonObject) reply.result().body(); (3)
  6. context.put("title", "Wiki home");
  7. context.put("pages", body.getJsonArray("pages").getList());
  8. templateEngine.render(context.data(), "templates/index.ftl", ar -> {
  9. if (ar.succeeded()) {
  10. context.response().putHeader("Content-Type", "text/html");
  11. context.response().end(ar.result());
  12. } else {
  13. context.fail(ar.cause());
  14. }
  15. });
  16. } else {
  17. context.fail(reply.cause());
  18. }
  19. });
  20. }
  1. The vertx object gives access to the event bus, and we send a message to the queue for the database verticle.

  2. Delivery options allow us to specify headers, payload codecs and timeouts.

  3. Upon success a reply contains a payload.

As we can see, an event bus message consists of a body, options, and it can optionally expect a reply. In the event that no response is expected there is a variant of the send method that does not have a handler.

We encode payloads as JSON objects, and we specify which action the database verticle should do through a message header called action.

The rest of the verticle code consists in the router handlers that also use the event-bus to fetch and store data:

  1. private static final String EMPTY_PAGE_MARKDOWN =
  2. "# A new page\n" +
  3. "\n" +
  4. "Feel-free to write in Markdown!\n";
  5. private void pageRenderingHandler(RoutingContext context) {
  6. String requestedPage = context.request().getParam("page");
  7. JsonObject request = new JsonObject().put("page", requestedPage);
  8. DeliveryOptions options = new DeliveryOptions().addHeader("action", "get-page");
  9. vertx.eventBus().request(wikiDbQueue, request, options, reply -> {
  10. if (reply.succeeded()) {
  11. JsonObject body = (JsonObject) reply.result().body();
  12. boolean found = body.getBoolean("found");
  13. String rawContent = body.getString("rawContent", EMPTY_PAGE_MARKDOWN);
  14. context.put("title", requestedPage);
  15. context.put("id", body.getInteger("id", -1));
  16. context.put("newPage", found ? "no" : "yes");
  17. context.put("rawContent", rawContent);
  18. context.put("content", Processor.process(rawContent));
  19. context.put("timestamp", new Date().toString());
  20. templateEngine.render(context.data(), "templates/page.ftl", ar -> {
  21. if (ar.succeeded()) {
  22. context.response().putHeader("Content-Type", "text/html");
  23. context.response().end(ar.result());
  24. } else {
  25. context.fail(ar.cause());
  26. }
  27. });
  28. } else {
  29. context.fail(reply.cause());
  30. }
  31. });
  32. }
  33. private void pageUpdateHandler(RoutingContext context) {
  34. String title = context.request().getParam("title");
  35. JsonObject request = new JsonObject()
  36. .put("id", context.request().getParam("id"))
  37. .put("title", title)
  38. .put("markdown", context.request().getParam("markdown"));
  39. DeliveryOptions options = new DeliveryOptions();
  40. if ("yes".equals(context.request().getParam("newPage"))) {
  41. options.addHeader("action", "create-page");
  42. } else {
  43. options.addHeader("action", "save-page");
  44. }
  45. vertx.eventBus().request(wikiDbQueue, request, options, reply -> {
  46. if (reply.succeeded()) {
  47. context.response().setStatusCode(303);
  48. context.response().putHeader("Location", "/wiki/" + title);
  49. context.response().end();
  50. } else {
  51. context.fail(reply.cause());
  52. }
  53. });
  54. }
  55. private void pageCreateHandler(RoutingContext context) {
  56. String pageName = context.request().getParam("name");
  57. String location = "/wiki/" + pageName;
  58. if (pageName == null || pageName.isEmpty()) {
  59. location = "/";
  60. }
  61. context.response().setStatusCode(303);
  62. context.response().putHeader("Location", location);
  63. context.response().end();
  64. }
  65. private void pageDeletionHandler(RoutingContext context) {
  66. String id = context.request().getParam("id");
  67. JsonObject request = new JsonObject().put("id", id);
  68. DeliveryOptions options = new DeliveryOptions().addHeader("action", "delete-page");
  69. vertx.eventBus().request(wikiDbQueue, request, options, reply -> {
  70. if (reply.succeeded()) {
  71. context.response().setStatusCode(303);
  72. context.response().putHeader("Location", "/");
  73. context.response().end();
  74. } else {
  75. context.fail(reply.cause());
  76. }
  77. });
  78. }