The vert.x is a tool-kit for building async applications on the JVM. Vert.x has a set of coure functions and a set of extensions for high level functions. The Vert.x core APIs are implemented in multiple languages including Scala. Vert.x-Web is a set of constructs for building web applications. Both the core and the web are embeddable.

1 Introduction

The functions of Vert.x core include

  • TCP/HTTP/WebSocket/UDP clients and servers, DNS client
  • Event bus
  • Shared data: local maps and clustered distributed maps
  • Periodic and delayed actions
  • Deploying and undeploying Verticles
  • File system access
  • Clustering

High leve functions such as db acess, authorization, web routing etc are in Vert.x extensions.

Vert.x is event-driven and uses serveral event loops, so called Multi-Reactor Pattern. An event handler will be executed in the exact same loop thread. A Vert.x inscance maintains N (by default is core * 2) event loop threads.

All Vert.x core APIs are asynchronouse except several file system operations ending in Sync. Use executeBloding to run blocking operation. Calling it several times from the same context executes serially. Specifying ordered = false makes to run them parallely. Another way is using a wroker verticle. Blocking code is executed on Vert.x work pool, additional pools can be created.

Vert.x use futures to coordinate async restuls.

2 Components

Vert.x comes with an optional actor-like deployment and concurrency model. Verticles can be a standard verticle running in the vent loop thread or a worker verticle running in the worker thread pool.

If you embed Vert.x and finish with it, you can call close to close it down. An event loop has a Context that can be retrieved by vertx.getOrCreateContext(). You can use Context#runOnContext to run code in this context asynchornously. You can use put and get to create context data shared by code in the same context.

A Vert.x instance has an event bus that forms a distributed p2p messaging system spanning mulitple verver nodes and multiple browsers. The event bus supports publish/subscribe, p2p, and request-response messaging.

Vert.x comes with a JsonObject, a JsonArray and JsonPointer to process JSON data.

Most data is shuffled around inside Vert.x using Buffer.

2.1 Running Blocking Code

Use Vertx#executeBlocking to run blocking code. The method signature is as following:

1
<T> void executeBlocking(Handler<Promise<T>> blockingCodeHandler, boolean ordered, Handler<AsyncResult<@Nullable T>> resultHandler);

It executes the blockingCodeHandler on a worker pool thread. In the blockingCodeHandler the current context remains the original context and therefore any task scheduled in it will be executed on the this context and not on the worker thread. By default, the ordered is true and the blocking code is executed serially. If the ordered is false, the blocking code is executed in parallel on the worker pool. The resultHandler is called when the blocking code is completed. resultHandler runs in the original context, i.e., the vertx message loop thread.

An alternative way to run blocking code is to use a woker verticle.

When the blocking operation lasts more than the 10 seconds, a message will be printed on the console by the blocked thread checker. Long blocking operations should use a dedicated thread managed by the application.

3 HTTP Server

3.1 A Simple Server

The build.sc file. Vert.x V4 will support Scala 2.13.

1
2
3
4
5
6
7
8
9
import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION`

import mill.Agg
import mill.scalalib.{DepSyntax, ScalaModule}

object hi extends ScalaModule {
  def scalaVersion = "2.13.3"
  def ivyDeps = Agg(ivy"io.vertx:vertx-core:3.9.1")
}

The hi/src/Hi.Scala`:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import io.vertx.core.Vertx
import io.vertx.core.http._

object Hi {
  def main(args: Array[String]): Unit = {
    // Vertx is the control center
    val vertx = Vertx.vertx()

    val httpServer = vertx.createHttpServer()
    httpServer.requestHandler(request => {
      request.response().end("Hi")
    })

    httpServer.listen(8088)
  }
}

You can set HttpServerOptions to set the options such as compression, idle timeout etc.

3.2 Request

The request has a type of HttpServerResponse. It has many methods.

The uri returns the URI such as a/b/c/page.html?param1=abc&param2=xyz, the path returns /a/b/c/page.html and query returns param1=abc&param2=xyz. Other request methods include method, headers (a MultiMap type that allows multiple values for the same case-insensitive key), host, params (a MultiMap type), absoluteURI, endHandler.

To receive body, use request.handler(buffer -> ...). The handler is called every time a chunk of the request body arrives. It is possible to cache the buffer and process the whole in request.endHandler(...).

Use getCookie to retrieve a cookie by name. Use addCookie to add a cookie.

3.3 Response

The response has a type of HttpServerResponse that is obtained by request.Response(). It is a WriteStream that you can pipe to it from any ReadStream. Some methods are:

  • setStatusCode
  • wirte: needs to write Content-Length first if you are not suing HTTP chunking.
  • end: can take a strin or a buffer.
  • setHeader or headers.set()
  • setChunked: to send data when size is not known in advance.
  • sendFile: or open a file as an AsyncFile and pipe it to the HTTP response, or readFile and write it to the response.

A send file Java example:

1
2
3
4
5
6
7
8
9
vertx.createHttpServer().requestHandler(request -> {
  String file = "";
  if (request.path().equals("/")) {
    file = "index.html";
  } else if (!request.path().contains("..")) {
    file = request.path();
  }
  request.response().sendFile("web/" + file);
}).listen(8080);

4 Vert.x Web Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// for bloop work properly in IDE
import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION`

import mill.Agg
import mill.scalalib.{DepSyntax, ScalaModule}

object app extends ScalaModule {
  def scalaVersion = "2.13.2"
  def ivyDeps = Agg(ivy"io.vertx:vertx-web:3.9.1")
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io.vertx.core.Vertx
import io.vertx.core.http._
import io.vertx.ext.web.Router

object app {
  def main(args: Array[String]): Unit = {
    // Vertx is the control center
    val vertx = Vertx.vertx()
    val router = Router.router(vertx);

    router
      .route() // This handler will be called for every request
      .handler(routingContext => {
        val response = routingContext.response()
        response.putHeader("content-type", "text/plain")
        response.end("Hi")
      })

    val server = vertx.createHttpServer()
    server.requestHandler(router).listen(8080);
  }
}

5 Vert.x Web Features

The key features are

  • Touting based on method, path, MIME types, etc.
  • Sub-routers and reroute.
  • Path matching by regular expreesion and path parmater extraction.
  • Cookie parsing and handling.
  • Session support.
  • Error page handler.
  • Auth handlers.
  • Template engine support.
  • CORS and CSRF support.

5.1 Router

Router is the core concept of Vert.x-Web. A Router has zero or more Routes. A router takes an HTTP request and passes it to the first matching route. The router has a handler associated with it. The handler can process the request, then either end it or pass it to the next matching handler. The next handler could be a hanlder in the same route or in another mathcing route. A handler takes a RoutingContext that contains the standard Vert.x HttpServerRequest and HttpServerResponse. The same routing context instance is passed to all handlers of the same request.

For blocking code, use blockingHandler to call the blocking handler. Set ordered = false if the order of blocing code doesn’t matter. To process multipart form data, use a non-blocking handler first to call request.setExecptMultipart(true).

Route matching is based on the method, path and MIME type of a request. You can specify the order of route matching.

You can set the path of a route using route("/some/path") or route().path("/some/path"). The path can include a * to match a path that has a certain prefix. Use "/some/path/:param1/:param2" to capture a path parameter. Use pathRegex to match path or capturing path parameters by regular expressions.

Use method(HttpMethod.Post) to match an HTTP Post method. By default, a route matches all methods. You can also use router.get("/some/path") or router.getWithRegex("*.some") to match a Get method. To match multiple methods, call the method multiple times.

Use consumes and produces method to specify matching MIME type – content-based routing. consumes matches content-type header in request. Wildcard char of * is supported, for example: router.route().consume("text/*"). You can match sub-type such as "*/json". Use produces method to specify the MIME type(s) to match the request accept header. Both consumes and produces can be called multiple times.

By default routes are matched in the order they are added to the router. Call the route’s order method to define an order, a smaller number (can be negative) is called before a bigger number. Use the last method to make a route to be the last.

5.2 Route Functions

You can disable or enable a route.

User RoutingContext#put and RoutingContext#get to put and get data for the lifetime of the request.

Use RoutingContext#reroute("/some/path") to reroute a request. Use RoutingContext to share data.

Use `Router.mountSubRouter(“/mount/path”, subRouter) to mount a subrouter.

Route match failures include 404, 405, 406, 415, and 400 for unmatched path, unmatched method, unmatched response content type, unmatched request content type and empty body errors.

You can set failure handlers for any route. Failure routing will occur if a handler throws an exception (500 failure), or if a handler calls fail with an HTTP failure status code. Use RoutingContext#statusCode and RoutingContext#failure to retrieve status code or the exception (the status code is -1).

The BodyHandler let you retrieve request bodies, limit body sizes and handle file uploads. It should be the first hanlder because the router needs to install handlers to consume HTTP reqeust body and this should be before executing any async call. If an async call must happen first, use request.paus() and request.resume() before and after the async all. Use getBodyAsJson to get JSON body, getBodyAsString for sting body and getBody to get a buffer. By default, form attributes are merged into request parameters.

Use RoutingContext#getCookie and RoutingContext#setCookie to get and set cookies. Vert.x-Web uses session cookies to identify a session. The cookie is just an UUID and the actuall session data is stored on the server. Local session stores are implemented by using a shared local map, and have a reaper which clears out expired sessions. Clustered session store uses a distributed map.

Use StaticHandler to serve static resources. The default static file directory is webroot. It is configurable using setWebRoot. It supports Content-Range header.

Use TemplateHandler to call a TemplateEngine. By default templates are located in templates directory. The handler will return a result of rendering with a content type of text/html by default.

The ResponseContentTypeHandler can set the appriopriate Content-Type of response based on the accept of request.