Vert.x (Java 8 Flavored)

Vert.x (Java 8 Flavored)

by Tim Dalton, Principal Software Engineer

September 2014


Introduction

Vert.x is a platform for running HTTP and TCP services. These services are normally implemented using callbacks that handle the results of otherwise blocking operations such as communicating with a database or file I/O. This if often referred to as "event-driven" processing and has been popularized by Node/JS. Use of event-driven services allows for greater scalability due to greater efficiency using threads. A single thread can be used by many services. Vert.x brings event-driven services to the JVM.

Below is a example of a very simple web server implemented in Vert.x:

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.core.Handler;
  4. import org.vertx.java.core.http.HttpServerRequest;
  5. import org.vertx.java.platform.Verticle;
  6.  
  7. public class SimpleExampleNoLambdas extends Verticle {
  8.  
  9. @Override
  10. public void start() {
  11. vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
  12. @Override
  13. public void handle(HttpServerRequest httpServerRequest) {
  14. httpServerRequest.response().end("Hello World");
  15. }
  16. }).listen(8080);
  17. }
  18. }

The above example creates a simple HTTP server that listens on port 8080 and returns a text response of "Hello World".

Though Vert.x was created before Java 8 was released, many of the interfaces have a single abstract method that allows them to be replaced with lambda expressions. Below is the same as above with the handler replaced with a lambda expression:

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.platform.Verticle;
  4.  
  5. public class SimpleExample extends Verticle {
  6.  
  7. @Override
  8. public void start() {
  9. vertx.createHttpServer().requestHandler(httpServerRequest -> {
  10. httpServerRequest.response().end("Hello World");
  11. }).listen(8080);
  12. }
  13. }

Though Vert.x does feature a command line interface for starting instances, there is also a Maven archetype to allow Vert.x to run via Maven only. The attached zip uses Maven so there is no need to download Vert.x itself to execute the examples, just Maven. To execute the examples, the JAVA_HOME environment variable needs be set to the location of Java 8 on the file system. Here are the Maven commands from the previous examples:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.SimpleExampleNoLambdas"
mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.SimpleExample"

Open http://localhost:8080 to interact with the above examples.

Verticles

The basic "unit" of processing within Vert.x is called a Verticle. Most of the time verticles are event-driven and handlers spawned from them should not block since other verticles in that instance share the same thread. Verticles can be deployed as "Worker" verticles that can perform blocking operations by running within their own thread pool. For this reason worker verticles are not as scalable as event-driven verticles. Many verticles can deployed within an instance of Vert.x.

Event Bus

Vert.x uses an event bus by which verticles can communicate with each other. Addresses for messages on the event bus are simply strings. Communication can be done via publish and subscribe or point to point. Message types supported include string, primitive types and instances of a buffer type implemented in Vert.x.

Below is example of multiple message handlers for the same address on the event bus. When messages are sent point to point to multiple handlers, the handlers will be invoked in round robin manner. Messages that are published are sent to all handlers.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.platform.Verticle;
  4.  
  5. public class MessagingExample extends Verticle {
  6.  
  7. @Override
  8. public void start() {
  9. container.deployVerticle("tfd.sett.vertx.MessagingExample$Server");
  10.  
  11. vertx.eventBus().registerHandler("message.handler", message -> {
  12. System.out.println("Handler 1 got a message = " + message.body());
  13. });
  14.  
  15. vertx.eventBus().registerHandler("message.handler", message -> {
  16. System.out.println("Handler 2 got a message = " + message.body());
  17. });
  18.  
  19. vertx.eventBus().registerHandler("message.handler", message -> {
  20. System.out.println("Handler 3 got a message = " + message.body());
  21. });
  22. }
  23.  
  24. public static class Server extends Verticle {
  25. @Override
  26. public void start() {
  27. vertx.createHttpServer().requestHandler(httpServerRequest -> {
  28. if (httpServerRequest.params().contains("send")) {
  29. vertx.eventBus().send("message.handler", httpServerRequest.params().get("send"));
  30. }
  31. if (httpServerRequest.params().contains("publish")) {
  32. vertx.eventBus().publish("message.handler", httpServerRequest.params().get("publish"));
  33. }
  34. httpServerRequest.response().end("OK");
  35. }).listen(8080);
  36. }
  37. }
  38. }

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.MessagingExample"

Using a browser, messages get sent to the even bus via urls like http://localhost:8080?send=this+has+been+sent 
and http://localhost:8080?publish=this+has+been+published.

One use for the event bus is when an event driven verticle needs to send work to a worker verticle that performs a blocking operation. The below example shows a verticle deploying two other verticles, one of which is a worker.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.core.Handler;
  4. import org.vertx.java.core.eventbus.Message;
  5. import org.vertx.java.platform.Verticle;
  6.  
  7. public class WorkerVerticleExample extends Verticle {
  8. @Override
  9. public void start() {
  10. container.deployVerticle("tfd.sett.vertx.WorkerVerticleExample$Server");
  11. container.deployWorkerVerticle("tfd.sett.vertx.WorkerVerticleExample$Worker");
  12. }
  13.  
  14. public static class Server extends Verticle {
  15. @Override
  16. public void start() {
  17. vertx.createHttpServer().requestHandler(httpServerRequest -> {
  18. vertx.eventBus().send("worker.handler", httpServerRequest.path(), (Handler<message<string>>) reply -> {
  19. httpServerRequest.response().end(reply.body());
  20. });
  21. }).listen(8080);
  22. }
  23. }
  24.  
  25. public static class Worker extends Verticle {
  26. @Override
  27. public void start() {
  28. vertx.eventBus().registerHandler("worker.handler", message -> {
  29. try { Thread.sleep(1000); } catch (InterruptedException ex) { }
  30. message.reply("Hello World : " + message.body().toString());
  31. });
  32. }
  33. }
  34. }
  35. </message<string>

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.WorkerVerticleExample"

In the above example, the Server verticle receives an HTTP request and sends a message to the Worker verticle which has a blocking operation (Thread.sleep). Upon completion, the worker verticle's reply is sent back to the server vertical to be included in the HTTP response.

Shared Data

Vert.x supports sharing of maps and sets between multiple verticles. These data structures are copied when retrieved to prevent multiple verticles from mutating the contents.

Below is an example of a handler in two different verticles communicating using a shared map. The response will be the path on the other handler's most recent request or blank if there isn't one. Run this example and open two browser tabs to http://localhost:8080/foo and http://localhost:8081/bar. Refresh the browser and change paths to see the data being shared being the verticles.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.platform.Verticle;
  4.  
  5. public class SharedData extends Verticle {
  6.  
  7. @Override
  8. public void start() {
  9. container.deployVerticle("tfd.sett.vertx.SharedData$Server1");
  10. container.deployVerticle("tfd.sett.vertx.SharedData$Server2");
  11.  
  12. }
  13.  
  14. public static class Server1 extends Verticle {
  15. @Override
  16. public void start() {
  17. vertx.createHttpServer().requestHandler(httpServerRequest -> {
  18. if (!"/favicon.ico".equals(httpServerRequest.path())) {
  19. vertx.sharedData().getMap("map").put("server1", httpServerRequest.path());
  20. }
  21. Object server2Path = vertx.sharedData().getMap("map").get("server2");
  22. httpServerRequest.response().end(server2Path != null ? server2Path.toString() : "");
  23. }).listen(8080);
  24. }
  25. }
  26.  
  27. public static class Server2 extends Verticle {
  28. @Override
  29. public void start() {
  30. vertx.createHttpServer().requestHandler(httpServerRequest -> {
  31. if (!"/favicon.ico".equals(httpServerRequest.path())) {
  32. vertx.sharedData().getMap("map").put("server2", httpServerRequest.path());
  33. }
  34. Object server1Path = vertx.sharedData().getMap("map").get("server1");
  35. httpServerRequest.response().end(server1Path != null ? server1Path.toString() : "");
  36. }).listen(8081);
  37. }
  38. }
  39. }

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.SharedData"

Multiple Instances

Vert.x supports multiple instances to scale across multiple cores. Usually the number of instances will be the number of cores on the host processor. Verticle instances are single-threaded and will only use a single processor core. Multiple instances allow Vert.x to take advantage of multiple cores. The number of instances can be specified as an argument when starting a verticle:

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.SimpleExample -instances 4"

Vert.x will handle multiplexing requests between the same verticle in different instances in a round robin manner.

Routing

Vert.x has a RouteMatcher class where different handlers can be registered for different paths on the HTTP request. Route matchers have methods corresponding to the various HTTP actions (GET, POST, etc) and can match on specific paths or a regular expression. There is a noMatch method where a handler can be specified to handle any request not matched.

The example below uses a RouteMatcher to serve up the "favicon.ico" from the file system and let other requests return the "Hello World" string:

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.core.http.RouteMatcher;
  4. import org.vertx.java.platform.Verticle;
  5.  
  6. public class RouteMatcherExample extends Verticle {
  7. @Override
  8. public void start() {
  9. RouteMatcher routeMatcher = new RouteMatcher();
  10.  
  11. routeMatcher.get("/favicon.ico", req -> {
  12. req.response().sendFile(this.getClass().getResource("/heart.ico").getFile().replaceAll("^/[A-Z]:", ""));
  13. });
  14.  
  15. routeMatcher.noMatch(req -> {
  16. req.response().end("Hello World");
  17. });
  18.  
  19. vertx.createHttpServer().requestHandler(routeMatcher).listen(8080);
  20. }
  21. }

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.RouteMatcherExample"

Navigating to http://localhost:8080/ should display a heart shaped icon on the tab or window provided a reasonably modern browser is being used.

Timers

Vert.x supports timers that can invoke handlers on one time basis after an initial delay or on a periodic basis.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.platform.Verticle;
  4.  
  5. public class TimerExample extends Verticle {
  6.  
  7. @Override
  8. public void start() {
  9. vertx.setTimer(15000, timerId -> {
  10. System.out.println("Fifteen seconds later...");
  11. });
  12.  
  13. vertx.setPeriodic(3000, timerId -> {
  14. System.out.println("Every three seconds this is printed");
  15. });
  16. }
  17. }

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.TimerExample"

TCP Servers

Vert.x is more than just HTTP. Support for TCP (and UDP) servers in built into Vert.x.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.core.net.NetServer;
  4. import org.vertx.java.platform.Verticle;
  5.  
  6. public class NetServerExample extends Verticle {
  7. @Override
  8. public void start() {
  9. NetServer netServer = vertx.createNetServer();
  10.  
  11. netServer.connectHandler(sock -> {
  12. System.out.println("A client has connected!");
  13.  
  14. sock.dataHandler(buffer -> {
  15. System.out.print(buffer.toString());
  16. });
  17.  
  18. sock.closeHandler( aVoid -> {
  19. System.out.println("A client has closed");
  20. });
  21. });
  22.  
  23. netServer.listen(8082);
  24. }
  25. }
mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.NetServerExample"

One can connect to the NetServer in the example above using telnet:

telnet localhost 8082

The server simply echoes characters typed in the telnet client.

Clustering

Vert.x allows multiple JVM instances to form a cluster sharing the event bus.

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.platform.Verticle;
  4.  
  5. public class RemoteHandlerExample extends Verticle {
  6.  
  7. @Override
  8. public void start() {
  9. vertx.eventBus().registerHandler("message.handler", message -> {
  10. System.out.println("Remote Handler 1 got a message = " + message.body());
  11. });
  12. }
  13. }

To execute this example along with the MessageExample above in a cluster use two command prompts:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.MessagingExample -cluster"
mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.RemoteHandlerExample -cluster"

Client-Side Event Bus

The event bus in Vert.x can be accessed by Javascript code running in the client. This allows the client to send and receive messages from verticles running on the server. To accomplish this Vert.x implements a SockJS server to send and receive data from the client:

  1. package tfd.sett.vertx;
  2.  
  3. import org.vertx.java.core.Handler;
  4. import org.vertx.java.core.http.HttpServer;
  5. import org.vertx.java.core.http.HttpServerRequest;
  6. import org.vertx.java.core.http.RouteMatcher;
  7. import org.vertx.java.core.json.JsonArray;
  8. import org.vertx.java.core.json.JsonObject;
  9. import org.vertx.java.platform.Verticle;
  10.  
  11. public class ClientMessagingExample extends Verticle {
  12. @Override
  13. public void start() {
  14. HttpServer server = vertx.createHttpServer().requestHandler(
  15. new RouteMatcher()
  16. .get("/vertxbus-2.1.js", new FileHandler("/vertxbus-2.1.js"))
  17. .get("/favicon.ico", new FileHandler("/heart.ico"))
  18. .noMatch(new FileHandler("/clientevents.html"))
  19. );
  20.  
  21. vertx.eventBus().registerHandler("message.handler", message -> {
  22. System.out.println("ClientMessagingExample got a message = " + message.body());
  23. });
  24.  
  25. JsonArray permitted = new JsonArray().add(new JsonObject().putString("address", "message.handler"));
  26. vertx.createSockJSServer(server).bridge(new JsonObject().putString("prefix", "/eventbus"), permitted, permitted);
  27. server.listen(8080);
  28. }
  29.  
  30. public static class FileHandler implements Handler<HttpServerRequest> {
  31. private final String file;
  32.  
  33. public FileHandler(String file) {
  34. this.file = file;
  35. }
  36.  
  37. @Override
  38. public void handle(HttpServerRequest httpServerRequest) {
  39. httpServerRequest.response().sendFile(this.getClass().getResource(file).getFile().replaceAll("^/[A-Z]:", ""));
  40. }
  41. }
  42. }

Here is some JavaScript to send and publish events:

  1. var eventBus;
  2.  
  3. function registerEventBus() {
  4. eventBus= new vertx.EventBus('http://localhost:8080/eventbus');
  5. eventBus.onopen = function() {
  6. eventBus.registerHandler('message.handler', function(message) {
  7. alert(message);
  8. });
  9. }
  10. }
  11.  
  12. function sendMessage() {
  13. eventBus.send('message.handler', 'sent from the client');
  14. }
  15.  
  16. function publishMessage() {
  17. eventBus.publish('message.handler', 'published by the client');
  18. }

To execute:

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.ClientMessagingExample"

Open http://localhost:8080/ in browser and hit "send" and "publish" buttons.

The ClientMessageExample can be executed in a cluster along with the RemoteHandlerExample. Since the "message.handler" is used for the event bus address for both, the messages should get distributed.

mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.ClientMessagingExample -cluster"
mvn compile exec:java -Dexec.mainClass="org.vertx.java.platform.impl.cli.Starter" -Dexec.args="run tfd.sett.vertx.RemoteHandlerExample -cluster"

Other Features

Notable features of Vert.x not covered here.

Module System

Re-usable collections of verticles and other components can be grouped into modules. Modules can be composed to within an Vert.x application. They also provide an extension mechanism for Vert.x.

Polyglot

Vert.x has modules to support verticles written in Groovy, Python (Jython), Ruby (JRuby) and Javascript (Rhino). Clojure and Scala support are planned as well.

Summary

Vert.x is good candidate for uses where HTTP as well TCP and UDP services need to be scalable across multiple hosts or embedded in a larger application. Another strong characteristic is simplicity when it comes to implementing services and handling concurrency.

References

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.