PolarSPARC

Introduction to Vert.x - Part 2


Bhaskar S 05/12/2019


Overview

In Part-1 of this series, we introduced the concept of Reactive systems, explored the core concepts as well as the high-level architectural view of Vert.x and got our hands dirty with some simple examples using Vert.x in Java.

In this part, we will continue with more examples.

Hands-on with Vert.x - 2

In the previous Sample02.java code, we had hardcoded the HTTP port to 8080 . Real applications use configuration file(s) to specify desired values. In the following example, we will make use of a config file to specify the HTTP port.

For this, we will need to include an additional library vertx-config from the Vertx toolkit.

The following is the modified listing of the Maven project file pom.xml that includes the additional library vertx-config as a dependency:

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  
  <groupId>com.polarsparc</groupId>
  <artifactId>Vertx</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <name>Vertx</name>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <fork>true</fork>
            <meminitial>128m</meminitial>
            <maxmem>512m</maxmem>
            <source>1.8</source>
            <target>1.8</target>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <dependencies>
    <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-core</artifactId>
        <version>3.7.0</version>
    </dependency>  
    <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-config</artifactId>
        <version>3.7.0</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

The following is the listing for the config file conf/config.json in the JSON format:

conf/config.json
{
    "http.port"=8080        
}

Note the above config file needs to be in the Java classpath.

The following is the listing for Sample03.java:

Sample03.java
/*
 * Topic:  Introduction to Vert.x
 * 
 * Name:   Sample 3
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.Vertx;

import java.util.logging.Level;
import java.util.logging.Logger;

import io.vertx.config.ConfigRetriever;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

public class Sample03 {
    private static Logger LOGGER = Logger.getLogger(Sample03.class.getName());
    
    private static String HTML = "<html>" +
                                   "<head><title>HttpServer Verticle</title></head>" +
                                   "<body><center>" +
                                   "<h3><font color='red'>Vert.x is AWESOME !!!</font></h3>" +
                                   "</center></body>" +
                                   "</html>";
    
    private static class HttpServerVerticle extends AbstractVerticle {
        @Override
        public void start(Future<Void> fut) {
            ConfigRetriever retriever = ConfigRetriever.create(vertx);
            retriever.getConfig(json -> {
                if (json.succeeded()) {
                    JsonObject config = json.result();
                    
                    int port = config.getInteger("http.port");
                    
                    LOGGER.log(Level.INFO, "Server port: " + port);
                    
                    vertx.createHttpServer()
                         .requestHandler(req -> {
                             LOGGER.log(Level.INFO, "Request from port: " + req.remoteAddress().port());
                             req.response()
                                 .putHeader("Content-Type", "text/html")
                                 .end(HTML);
                         })
                         .listen(port, res -> {
                             if (res.succeeded()) {
                                 fut.complete();
                             } else {
                                 fut.fail(res.cause());
                             }
                         });
                    
                    LOGGER.log(Level.INFO, "Started http server on localhost:" + port + "...");
                } else {
                    json.cause().printStackTrace();
                }
            });
        }
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new HttpServerVerticle(), res -> {
            if (res.succeeded()) {
                LOGGER.log(Level.INFO, "Deployed instance ID: " + res.result());
            } else {
                res.cause().printStackTrace();
            }
        });
    }
}

Let us explain and understand the code from Sample03 listed above.

The method create() on the class io.vertx.config.ConfigRetriever is used to create an instance of the configuration reader. By default, the configuration retriever looks for a file called conf/config.json in the classpath. One can use the environment variable VERTX_CONFIG_PATH to override the location of the config.json file.

The call to the method getConfig() on the instance of ConfigRetriever takes in a handler function as the callback, which is invoked once the config file is read. The handler function is an interface of type io.vertx.core.Handler<E>, where <E> is of type io.vertx.core.AsyncResult<JsonObject>. In Vertx, a JSON object is encapsulated and represented by the class io.vertx.core.json.JsonObject.

The call to the method getInteger(), passing in a string value of "http.port", on the instance of JsonObject fetches the value of HTTP port as specified in the config file.

Rest of the code is similar to the one from listing Sample02.java.

❗❗ ATTENTION ❗❗

Processing in Vertx is non-blocking and asynchronous through callbacks. Hence, the HTTP server start-up code is inside the callback of the configuration retriever

Executing the Java program Sample03 listed above should generate an output similar to the following:

Output.1

May 11, 2019 7:28:51 PM io.vertx.config.impl.ConfigRetrieverImpl
INFO: Config file path: /home/polarsparc/Vertx/src/resources/conf/config.json, format:json
May 11, 2019 7:28:52 PM com.polarsparc.Vertx.Sample03$HttpServerVerticle lambda$0
INFO: Server port: 8080
May 11, 2019 7:28:52 PM com.polarsparc.Vertx.Sample03$HttpServerVerticle lambda$0
INFO: Started http server on localhost:8080...
May 11, 2019 7:28:52 PM com.polarsparc.Vertx.Sample03 lambda$0
INFO: Deployed instance ID: 50632593-1427-4557-870b-d8fb1399e0b4

Launch a browser and access the url http://localhost:8080.

The following screenshot would be the typical view:

Browser
Figure.1

When the HTTP server responds back to the browser, it should generate the following additional output:

Output.2

May 11, 2019 7:30:01 PM com.polarsparc.Vertx.Sample03$HttpServerVerticle lambda$1
INFO: Request from port: 55732

One of the interesting side effects of the asynchronous/non-blocking callback based processing is that, it results in a deeply nested, complex code (often referred to as the callback hell).

One could simplify and streamline the code by chaining functions that return a io.vertx.core.Future (referred to as the future).

In other words, if we have two asynchronous methods, say, Future<A> method_a and Future<B> method_b(A) that each return a future, then one can invoke them as follows:

  method_a.compose(method_b).setHandler(res -> {...})

The method passed to compose(...) is invoked only if the future from the method method_a() completes successfully.

The following is the listing for Sample04.java, which is a simplified version of the code from Sample03; it uses chaining on the methods that return a Future:

Sample04.java
/*
 * Topic:  Introduction to Vert.x
 * 
 * Name:   Sample 4
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.Vertx;

import java.util.logging.Level;
import java.util.logging.Logger;

import io.vertx.config.ConfigRetriever;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Vertx;

public class Sample04 {
    private static Logger LOGGER = Logger.getLogger(Sample04.class.getName());
    
    private static String HTML = "<html>" +
                                   "<head><title>HttpServer Verticle</title></head>" +
                                   "<body><center>" +
                                   "<h3><font color='forestgreen'>Vert.x works <u>GREAT</u> !!!</font></h3>" +
                                   "</center></body>" +
                                   "</html>";
    
    private static class HttpServerVerticle extends AbstractVerticle {
        @Override
        public void start(Future<Void> fut) {
            ConfigRetriever retriever = ConfigRetriever.create(vertx);
            
            ConfigRetriever.getConfigAsFuture(retriever)
                .compose(config -> {
                    int port = config.getInteger("http.port");
                    
                    LOGGER.log(Level.INFO, "Configured server port: " + port);
                    
                    Future<Void> next = Future.future();
                    
                    vertx.createHttpServer()
                     .requestHandler(req -> {
                         LOGGER.log(Level.INFO, "Request from port: " + req.remoteAddress().port());
                         req.response()
                             .putHeader("Content-Type", "text/html")
                             .end(HTML);
                     })
                     .listen(port, res -> {
                         if (res.succeeded()) {
                             LOGGER.log(Level.INFO, "Started http server on localhost:" + port + "...");
                             
                             next.complete();
                         } else {
                             next.fail(res.cause());
                         }
                     });
                    
                    return next;
                })
                .setHandler(res -> {
                     if (res.succeeded()) {
                         fut.complete();
                     } else {
                         fut.fail(res.cause());
                     }
                });
        }
    }

    public static void main(String[] args) {
        Vertx vertx = Vertx.vertx();
        vertx.deployVerticle(new HttpServerVerticle(), res -> {
            if (res.succeeded()) {
                LOGGER.log(Level.INFO, "Deployed instance ID: " + res.result());
            } else {
                res.cause().printStackTrace();
            }
        });
    }
}

Let us explain and understand the code from Sample04 listed above.

The method getConfigAsFuture() on the class io.vertx.config.ConfigRetriever executes immediately and returns a future object of type Future<JsonObject>. The returned future completes when the config file is successfully retrieved.

The method compose(java.util.function.Function<T,Future<U>>) on the class io.vertx.core.Future is used to chain a mapper function, which is invoked when the future on which the compose() was invoked completes successfully. The mapper function is passed in the result of the completed future as an argument and returns another future object.

The method setHandler(Handler<AsyncResult<T>>) on the class io.vertx.core.Future takes in a handler function, which is invoked when the future on which it is was invoked completes successfully.

More to be covered in the next part of this series ... 😎

References

[1] Introduction to Vert.x - Part-1

[2] Vert.x Config Manual (Java)

[3] Vert.x Core Manual (Java)



© PolarSPARC