Writing TCP servers and clients

Vert.x allows you to easily write non-blocking TCP clients and servers.

Creating a TCP server

The simplest way to create a TCP server, using all default options is as follows:

NetServer server = vertx.createNetServer();

Configuring a TCP server

If you don’t want the default, a server can be configured by passing in a NetServerOptions instance when creating it:

NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);

Start the Server Listening

To tell the server to listen for incoming requests you use one of the listen alternatives.

To tell the server to listen at the host and port as specified in the options:

NetServer server = vertx.createNetServer();
server.listen();

Or to specify the host and port in the call to listen, ignoring what is configured in the options:

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");

The default host is 0.0.0.0 which means 'listen on all available addresses' and the default port is 0, which is a special value that instructs the server to find a random unused local port and use that.

The actual bind is asynchronous, so the server might not actually be listening until some time after the call to listen has returned.

If you want to be notified when the server is actually listening you can provide a handler to the listen call. For example:

NetServer server = vertx.createNetServer();
server
  .listen(1234, "localhost")
  .onComplete(res -> {
    if (res.succeeded()) {
      System.out.println("Server is now listening!");
    } else {
      System.out.println("Failed to bind!");
    }
  });

Listening on a random port

If 0 is used as the listening port, the server will find an unused random port to listen on.

To find out the real port the server is listening on you can call actualPort.

NetServer server = vertx.createNetServer();
server
  .listen(0, "localhost")
  .onComplete(res -> {
    if (res.succeeded()) {
      System.out.println("Server is now listening on actual port: " + server.actualPort());
    } else {
      System.out.println("Failed to bind!");
    }
  });

Listening to Unix domain sockets

When running on JDK 16+, or using a native transport, a server can listen to Unix domain sockets:

NetServer netServer = vertx.createNetServer();

// Only available when running on JDK16+, or using a native transport
SocketAddress address = SocketAddress.domainSocketAddress("/var/tmp/myservice.sock");

netServer
  .connectHandler(so -> {
  // Handle application
  })
  .listen(address)
  .onComplete(ar -> {
    if (ar.succeeded()) {
      // Bound to socket
    } else {
      // Handle failure
    }
  });

Getting notified of incoming connections

To be notified when a connection is made you need to set a connectHandler:

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
  // Handle the connection in here
});

When a connection is made the handler will be called with an instance of NetSocket.

This is a socket-like interface to the actual connection, and allows you to read and write data as well as do various other things like close the socket.

Reading data from the socket

To read data from the socket you set the handler on the socket.

This handler will be called with an instance of Buffer every time data is received on the socket.

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
  socket.handler(buffer -> {
    System.out.println("I received some bytes: " + buffer.length());
  });
});

Writing data to a socket

You write to a socket using one of write.

Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);

// Write a string in UTF-8 encoding
socket.write("some data");

// Write a string using the specified encoding
socket.write("some data", "UTF-16");

Write operations are asynchronous and may not occur until some time after the call to write has returned.

Closed handler

If you want to be notified when a socket is closed, you can set a closeHandler on it:

socket.closeHandler(v -> {
  System.out.println("The socket has been closed");
});

Handling exceptions

You can set an exceptionHandler to receive any exceptions that happen on the socket.

You can set an exceptionHandler to receive any exceptions that happens before the connection is passed to the connectHandler , e.g during the TLS handshake.

Event bus write handler

Every socket can register a handler on the event bus, and when any buffers are received in this handler, it writes them to itself. Those are local subscriptions, not reachable from other clustered nodes.

This enables you to write data to a socket which is potentially in a completely different verticle by sending the buffer to the address of that handler.

This feature is disabled by default, however you can enable it using setRegisterWriteHandler or setRegisterWriteHandler.

The address of the handler is given by writeHandlerID.

Local and remote addresses

The local address of a NetSocket can be retrieved using localAddress.

The remote address, (i.e. the address of the other end of the connection) of a NetSocket can be retrieved using remoteAddress.

Sending files or resources from the classpath

Files and classpath resources can be written to the socket directly using sendFile. This can be a very efficient way to send files, as it can be handled by the OS kernel directly where supported by the operating system.

Please see the chapter about serving files from the classpath for restrictions of the classpath resolution or disabling it.

socket.sendFile("myfile.dat");

Streaming sockets

Instances of NetSocket are also ReadStream and WriteStream instances, so they can be used to pipe data to or from other read and write streams.

See the chapter on streams for more information.

Upgrading connections to SSL/TLS

A non SSL/TLS connection can be upgraded to SSL/TLS using upgradeToSsl.

The server or client must be configured for SSL/TLS for this to work correctly. Please see the chapter on SSL/TLS for more information.

TCP graceful shut down

You can shut down a server or client.

Calling shutdown initiates the shut-down phase whereby the server or client are given the opportunity to perform clean-up actions and handle shutdown at the protocol level.

server
  .shutdown()
  .onSuccess(res -> {
    System.out.println("Server is now closed");
  });

Shut-down waits until all sockets are closed or the shut-down timeout fires. When the timeout fires, all sockets are forcibly closed.

Each opened socket is notified with a shutdown event, allowing to perform a protocol level close before the actual socket close.

socket.shutdownHandler(v -> {
  socket
    // Write close frame
    .write(closeFrame())
    // Wait until we receive the remote close frame
    .compose(success -> closeFrameHandler(socket))
    // Close the socket
    .eventually(() -> socket.close());
});

The default shut-down timeout is 30 seconds, you can override the amount of time

server
  .shutdown(60, TimeUnit.SECONDS)
  .onSuccess(res -> {
    System.out.println("Server is now closed");
  });

TCP close

You can close a server or client to immediately close all open connections and releases all resources. Unlike shutdown there is not grace period.

The close is actually asynchronous and might not complete until some time after the call has returned. You can use the returned future to be notified when the actual close has completed.

This future is completed when the close has fully completed.

server
  .close()
  .onSuccess(res -> {
    System.out.println("Server is now closed");
  });

Automatic clean-up in verticles

If you’re creating TCP servers and clients from inside verticles, those servers and clients will be automatically closed when the verticle is undeployed.

Scaling - sharing TCP servers

The handlers of any TCP server are always executed on the same event loop thread.

This means that if you are running on a server with a lot of cores, and you only have this one instance deployed then you will have at most one core utilised on your server.

In order to utilise more cores of your server you will need to deploy more instances of the server.

You can instantiate more instances programmatically in your code:

class MyVerticle extends VerticleBase {

  NetServer server;

  @Override
  public Future<?> start() {
    server = vertx.createNetServer();
    server.connectHandler(socket -> {
      socket.handler(buffer -> {
        // Just echo back the data
        socket.write(buffer);
      });
    });
    return server.listen(1234, "localhost");
  }
}

// Create a few instances so we can utilise cores
vertx.deployVerticle(MyVerticle.class, new DeploymentOptions().setInstances(10));

Once you do this you will find the echo server works functionally identically to before, but all your cores on your server can be utilised and more work can be handled.

At this point you might be asking yourself 'How can you have more than one server listening on the same host and port? Surely you will get port conflicts as soon as you try and deploy more than one instance?'

Vert.x does a little magic here.*

When you deploy another server on the same host and port as an existing server it doesn’t actually try and create a new server listening on the same host/port.

Instead it internally maintains just a single server, and, as incoming connections arrive it distributes them in a round-robin fashion to any of the connect handlers.

Consequently Vert.x TCP servers can scale over available cores while each instance remains single threaded.

Creating a TCP client

The simplest way to create a TCP client, using all default options is as follows:

NetClient client = vertx.createNetClient();

Configuring a TCP client

If you don’t want the default, a client can be configured by passing in a NetClientOptions instance when creating it:

NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);

Making connections

To make a connection to a server you use connect, specifying the port and host of the server and a handler that will be called with a result containing the NetSocket when connection is successful or with a failure if connection failed.

NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
client
  .connect(4321, "localhost")
  .onComplete(res -> {
    if (res.succeeded()) {
      System.out.println("Connected!");
      NetSocket socket = res.result();
    } else {
      System.out.println("Failed to connect: " + res.cause().getMessage());
    }
  });

Making connections to Unix domain sockets

When running on JDK 16+, or using a native transport, a client can connect to Unix domain sockets:

NetClient netClient = vertx.createNetClient();

// Only available when running on JDK16+, or using a native transport
SocketAddress addr = SocketAddress.domainSocketAddress("/var/tmp/myservice.sock");

// Connect to the server
netClient
  .connect(addr)
  .onComplete(ar -> {
    if (ar.succeeded()) {
      // Connected
    } else {
      // Handle failure
    }
  });

Configuring connection attempts

A client can be configured to automatically retry connecting to the server in the event that it cannot connect. This is configured with setReconnectInterval and setReconnectAttempts.

Currently, Vert.x will not attempt to reconnect if a connection fails, reconnect attempts and interval only apply to creating initial connections.
NetClientOptions options = new NetClientOptions().
  setReconnectAttempts(10).
  setReconnectInterval(500);

NetClient client = vertx.createNetClient(options);

By default, multiple connection attempts are disabled.

Logging network activity

For debugging purposes, network activity can be logged:

NetServerOptions options = new NetServerOptions().setLogActivity(true);

NetServer server = vertx.createNetServer(options);

Here is the output of a simple HTTP server

id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] READ: 78B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..|
|00000010| 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a |Host: localhost:|
|00000020| 38 30 38 30 0d 0a 55 73 65 72 2d 41 67 65 6e 74 |8080..User-Agent|
|00000030| 3a 20 63 75 72 6c 2f 37 2e 36 34 2e 31 0d 0a 41 |: curl/7.64.1..A|
|00000040| 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a       |ccept: */*....  |
+--------+-------------------------------------------------+----------------+
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] WRITE: 50B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
|00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
|00000020| 20 31 31 0d 0a 0d 0a 48 65 6c 6c 6f 20 57 6f 72 | 11....Hello Wor|
|00000030| 6c 64                                           |ld              |
+--------+-------------------------------------------------+----------------+
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] READ COMPLETE
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] FLUSH

By default, binary data is logged in hex format.

You can reduce the data format verbosity to only print the buffer length instead of the entire data by setting the log data fomat.

NetServerOptions options = new NetServerOptions()
  .setLogActivity(true)
  .setActivityLogDataFormat(ByteBufFormat.SIMPLE);

NetServer server = vertx.createNetServer(options);

Here is the same output with simple buffer format

[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ: 78B
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] WRITE: 50B
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ COMPLETE
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] FLUSH
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ COMPLETE
[id: 0xda8d41dc, L:/127.0.0.1:8080 ! R:/127.0.0.1:65399] INACTIVE
[id: 0xda8d41dc, L:/127.0.0.1:8080 ! R:/127.0.0.1:65399] UNREGISTERED

Clients can also log network activity

NetClientOptions options = new NetClientOptions().setLogActivity(true);

NetClient client = vertx.createNetClient(options);

Network activity is logged by Netty with the DEBUG level and with the io.netty.handler.logging.LoggingHandler name. When using network activity logging there are a few things to keep in mind:

  • logging is not performed by Vert.x logging but by Netty

  • this is not a production feature

You should read the [netty-logging] section.

Throttling inbound and outbound bandwidth of TCP connections

TCP server (Net/Http) can be configured with traffic shaping options to enable bandwidth limiting. Both inbound and outbound bandwidth can be limited through TrafficShapingOptions. For NetServer, traffic shaping options can be set through NetServerOptions and for HttpServer it can be set through HttpServerOptions.

NetServerOptions options = new NetServerOptions()
  .setHost("localhost")
  .setPort(1234)
  .setTrafficShapingOptions(new TrafficShapingOptions()
    .setInboundGlobalBandwidth(64 * 1024)
    .setOutboundGlobalBandwidth(128 * 1024));

NetServer server = vertx.createNetServer(options);
HttpServerOptions options = new HttpServerOptions()
  .setHost("localhost")
  .setPort(1234)
  .setTrafficShapingOptions(new TrafficShapingOptions()
    .setInboundGlobalBandwidth(64 * 1024)
    .setOutboundGlobalBandwidth(128 * 1024));

HttpServer server = vertx.createHttpServer(options);

These traffic shaping options can also be dynamically updated after server start.

NetServerOptions options = new NetServerOptions()
                             .setHost("localhost")
                             .setPort(1234)
                             .setTrafficShapingOptions(new TrafficShapingOptions()
                                                         .setInboundGlobalBandwidth(64 * 1024)
                                                         .setOutboundGlobalBandwidth(128 * 1024));
NetServer server = vertx.createNetServer(options);
TrafficShapingOptions update = new TrafficShapingOptions()
                                 .setInboundGlobalBandwidth(2 * 64 * 1024) // twice
                                 .setOutboundGlobalBandwidth(128 * 1024); // unchanged
server
  .listen(1234, "localhost")
  // wait until traffic shaping handler is created for updates
  .onSuccess(v -> server.updateTrafficShapingOptions(update));
HttpServerOptions options = new HttpServerOptions()
                              .setHost("localhost")
                              .setPort(1234)
                              .setTrafficShapingOptions(new TrafficShapingOptions()
                                                          .setInboundGlobalBandwidth(64 * 1024)
                                                          .setOutboundGlobalBandwidth(128 * 1024));
HttpServer server = vertx.createHttpServer(options);
TrafficShapingOptions update = new TrafficShapingOptions()
                                 .setInboundGlobalBandwidth(2 * 64 * 1024) // twice
                                 .setOutboundGlobalBandwidth(128 * 1024); // unchanged
server
  .listen(1234, "localhost")
  // wait until traffic shaping handler is created for updates
  .onSuccess(v -> server.updateTrafficShapingOptions(update));

Configuring servers and clients to work with SSL/TLS

TCP clients and servers can be configured to use Transport Layer Security - earlier versions of TLS were known as SSL.

The APIs of the servers and clients are identical whether or not SSL/TLS is used, and it’s enabled by configuring the NetClientOptions or NetServerOptions instances used to create the servers or clients.

Enabling SSL/TLS on the server

SSL/TLS is enabled with ssl.

By default it is disabled.

Specifying key/certificate for the server

SSL/TLS servers usually provide certificates to clients in order to verify their identity to clients.

Certificates/keys can be configured for servers in several ways:

The first method is by specifying the location of a Java key-store which contains the certificate and private key.

Java key stores can be managed with the keytool utility which ships with the JDK.

The password for the key store should also be provided:

NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions(
  new JksOptions().
    setPath("/path/to/your/server-keystore.jks").
    setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);

Alternatively you can read the key store yourself as a buffer and provide that directly:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.jks");
JksOptions jksOptions = new JksOptions().
  setValue(myKeyStoreAsABuffer).
  setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(jksOptions);
NetServer server = vertx.createNetServer(options);

Key/certificate in PKCS#12 format (http://en.wikipedia.org/wiki/PKCS_12), usually with the .pfx or the .p12 extension can also be loaded in a similar fashion than JKS key stores:

NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions(
  new PfxOptions().
    setPath("/path/to/your/server-keystore.pfx").
    setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);

Buffer configuration is also supported:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
  setValue(myKeyStoreAsABuffer).
  setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(pfxOptions);
NetServer server = vertx.createNetServer(options);

Another way of providing server private key and certificate separately using .pem files.

NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions(
  new PemKeyCertOptions().
    setKeyPath("/path/to/your/server-key.pem").
    setCertPath("/path/to/your/server-cert.pem")
);
NetServer server = vertx.createNetServer(options);

Buffer configuration is also supported:

Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
  setKeyValue(myKeyAsABuffer).
  setCertValue(myCertAsABuffer);
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(pemOptions);
NetServer server = vertx.createNetServer(options);

Vert.x supports reading of unencrypted RSA and/or ECC based private keys from PKCS8 PEM files. RSA based private keys can also be read from PKCS1 PEM files. X.509 certificates can be read from PEM files containing a textual encoding of the certificate as defined by RFC 7468, Section 5.

Keep in mind that the keys contained in an unencrypted PKCS8 or a PKCS1 PEM file can be extracted by anybody who can read the file. Thus, make sure to put proper access restrictions on such PEM files in order to prevent misuse.

Finally, you can also load generic Java keystore, it is useful for using other KeyStore implementations like Bouncy Castle:

NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions(
  new KeyStoreOptions().
    setType("BKS").
    setPath("/path/to/your/server-keystore.bks").
    setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);

Specifying trust for the server

SSL/TLS servers can use a certificate authority in order to verify the identity of the clients.

Certificate authorities can be configured for servers in several ways:

Java trust stores can be managed with the keytool utility which ships with the JDK.

The password for the trust store should also be provided:

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new JksOptions().
      setPath("/path/to/your/truststore.jks").
      setPassword("password-of-your-truststore")
  );
NetServer server = vertx.createNetServer(options);

Alternatively you can read the trust store yourself as a buffer and provide that directly:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new JksOptions().
      setValue(myTrustStoreAsABuffer).
      setPassword("password-of-your-truststore")
  );
NetServer server = vertx.createNetServer(options);

Certificate authority in PKCS#12 format (http://en.wikipedia.org/wiki/PKCS_12), usually with the .pfx or the .p12 extension can also be loaded in a similar fashion than JKS trust stores:

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new PfxOptions().
      setPath("/path/to/your/truststore.pfx").
      setPassword("password-of-your-truststore")
  );
NetServer server = vertx.createNetServer(options);

Buffer configuration is also supported:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new PfxOptions().
      setValue(myTrustStoreAsABuffer).
      setPassword("password-of-your-truststore")
  );
NetServer server = vertx.createNetServer(options);

Another way of providing server certificate authority using a list .pem files.

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new PemTrustOptions().
      addCertPath("/path/to/your/server-ca.pem")
  );
NetServer server = vertx.createNetServer(options);

Buffer configuration is also supported:

Buffer myCaAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-ca.pfx");
NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setClientAuth(ClientAuth.REQUIRED).
  setTrustOptions(
    new PemTrustOptions().
      addCertValue(myCaAsABuffer)
  );
NetServer server = vertx.createNetServer(options);

Enabling SSL/TLS on the client

Net Clients can also be easily configured to use SSL. They have the exact same API when using SSL as when using standard sockets.

To enable SSL on a NetClient the function setSSL(true) is called.

Client trust configuration

If the trustALl is set to true on the client, then the client will trust all server certificates. The connection will still be encrypted but this mode is vulnerable to 'man in the middle' attacks. I.e. you can’t be sure who you are connecting to. Use this with caution. Default value is false.

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustAll(true);
NetClient client = vertx.createNetClient(options);

If trustAll is not set then a client trust store must be configured and should contain the certificates of the servers that the client trusts.

By default, host verification is not configured on the client. This verifies the CN portion of the server certificate against the server hostname to avoid Man-in-the-middle attacks.

You must configure it explicitly on your client

  • "" (empty string) disables host verification

  • "HTTPS" enables HTTP over TLS verification

  • LDAPS enables LDAP v3 extension for TLS verification

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setHostnameVerificationAlgorithm(verificationAlgorithm);
NetClient client = vertx.createNetClient(options);
the Vert.x HTTP client uses the TCP client and configures with "HTTPS" the verification algorithm.

Like server configuration, the client trust can be configured in several ways:

The first method is by specifying the location of a Java trust-store which contains the certificate authority.

It is just a standard Java key store, the same as the key stores on the server side. The client trust store location is set by using the function path on the jks options. If a server presents a certificate during connection which is not in the client trust store, the connection attempt will not succeed.

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new JksOptions().
      setPath("/path/to/your/truststore.jks").
      setPassword("password-of-your-truststore")
  );
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new JksOptions().
      setValue(myTrustStoreAsABuffer).
      setPassword("password-of-your-truststore")
  );
NetClient client = vertx.createNetClient(options);

Certificate authority in PKCS#12 format (http://en.wikipedia.org/wiki/PKCS_12), usually with the .pfx or the .p12 extension can also be loaded in a similar fashion than JKS trust stores:

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new PfxOptions().
      setPath("/path/to/your/truststore.pfx").
      setPassword("password-of-your-truststore")
  );
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new PfxOptions().
      setValue(myTrustStoreAsABuffer).
      setPassword("password-of-your-truststore")
  );
NetClient client = vertx.createNetClient(options);

Another way of providing server certificate authority using a list .pem files.

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new PemTrustOptions().
      addCertPath("/path/to/your/ca-cert.pem")
  );
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/ca-cert.pem");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(
    new PemTrustOptions().
      addCertValue(myTrustStoreAsABuffer)
  );
NetClient client = vertx.createNetClient(options);

Specifying key/certificate for the client

If the server requires client authentication then the client must present its own certificate to the server when connecting. The client can be configured in several ways:

The first method is by specifying the location of a Java key-store which contains the key and certificate. Again it’s just a regular Java key store. The client keystore location is set by using the function path on the jks options.

NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions(
  new JksOptions().
    setPath("/path/to/your/client-keystore.jks").
    setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.jks");
JksOptions jksOptions = new JksOptions().
  setValue(myKeyStoreAsABuffer).
  setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setKeyCertOptions(jksOptions);
NetClient client = vertx.createNetClient(options);

Key/certificate in PKCS#12 format (http://en.wikipedia.org/wiki/PKCS_12), usually with the .pfx or the .p12 extension can also be loaded in a similar fashion than JKS key stores:

NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions(
  new PfxOptions().
    setPath("/path/to/your/client-keystore.pfx").
    setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
  setValue(myKeyStoreAsABuffer).
  setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setKeyCertOptions(pfxOptions);
NetClient client = vertx.createNetClient(options);

Another way of providing server private key and certificate separately using .pem files.

NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions(
  new PemKeyCertOptions().
    setKeyPath("/path/to/your/client-key.pem").
    setCertPath("/path/to/your/client-cert.pem")
);
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
  setKeyValue(myKeyAsABuffer).
  setCertValue(myCertAsABuffer);
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setKeyCertOptions(pemOptions);
NetClient client = vertx.createNetClient(options);

Keep in mind that pem configuration, the private key is not crypted.

Updating SSL/TLS configuration

You can use the updateSSLOptions method to update the key/certifications or trust on a TCP server or client (e.g. to implement certificate rotation).

Future<Boolean> fut = server.updateSSLOptions(new ServerSSLOptions()
  .setKeyCertOptions(
    new JksOptions()
      .setPath("/path/to/your/server-keystore.jks").
      setPassword("password-of-your-keystore")));

When the update succeeds the new SSL configuration is used, otherwise the previous configuration is preserved.

The options object is compared (using equals) against the existing options to prevent an update when the objects are equals since loading options can be costly. When object are equals, you can use the force parameter to force the update.

Self-signed certificates for testing and development purposes

Do not use this in production settings, and note that the generated keys are very insecure.

It is very often the case that self-signed certificates are required, be it for unit / integration tests or for running a development version of an application.

SelfSignedCertificate can be used to provide self-signed PEM certificate helpers and give KeyCertOptions and TrustOptions configurations:

SelfSignedCertificate certificate = SelfSignedCertificate.create();

NetServerOptions serverOptions = new NetServerOptions()
  .setSsl(true)
  .setKeyCertOptions(certificate.keyCertOptions())
  .setTrustOptions(certificate.trustOptions());

vertx.createNetServer(serverOptions)
  .connectHandler(socket -> socket.end(Buffer.buffer("Hello!")))
  .listen(1234, "localhost");

NetClientOptions clientOptions = new NetClientOptions()
  .setSsl(true)
  .setKeyCertOptions(certificate.keyCertOptions())
  .setTrustOptions(certificate.trustOptions());

NetClient client = vertx.createNetClient(clientOptions);
client
  .connect(1234, "localhost")
  .onComplete(ar -> {
    if (ar.succeeded()) {
      ar.result().handler(buffer -> System.out.println(buffer));
    } else {
      System.err.println("Woops: " + ar.cause().getMessage());
    }
  });

The client can also be configured to trust all certificates:

NetClientOptions clientOptions = new NetClientOptions()
  .setSsl(true)
  .setTrustAll(true);

Note that self-signed certificates also work for other TCP protocols like HTTPS:

SelfSignedCertificate certificate = SelfSignedCertificate.create();

vertx.createHttpServer(new HttpServerOptions()
  .setSsl(true)
  .setKeyCertOptions(certificate.keyCertOptions())
  .setTrustOptions(certificate.trustOptions()))
  .requestHandler(req -> req.response().end("Hello!"))
  .listen(8080);

Revoking certificate authorities

Trust can be configured to use a certificate revocation list (CRL) for revoked certificates that should no longer be trusted. The crlPath configures the crl list to use:

NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(trustOptions).
  addCrlPath("/path/to/your/crl.pem");
NetClient client = vertx.createNetClient(options);

Buffer configuration is also supported:

Buffer myCrlAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/crl.pem");
NetClientOptions options = new NetClientOptions().
  setSsl(true).
  setTrustOptions(trustOptions).
  addCrlValue(myCrlAsABuffer);
NetClient client = vertx.createNetClient(options);

Configuring the Cipher suite

By default, the TLS configuration will use the list of Cipher suites of the SSL engine:

This Cipher suite can be configured with a suite of enabled ciphers:

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions).
  addEnabledCipherSuite("ECDHE-RSA-AES128-GCM-SHA256").
  addEnabledCipherSuite("ECDHE-ECDSA-AES128-GCM-SHA256").
  addEnabledCipherSuite("ECDHE-RSA-AES256-GCM-SHA384").
  addEnabledCipherSuite("CDHE-ECDSA-AES256-GCM-SHA384");
NetServer server = vertx.createNetServer(options);

When the enabled cipher suites is defined (i.e not empty), it takes precedence over the default cipher suites of the SSL engine.

Cipher suite can be specified on the NetServerOptions or NetClientOptions configuration.

Configuring TLS protocol versions

By default, the default TLS configuration enables the following protocols: TLSv1.2 and TLSv1.3. Protocol versions can be enabled by explicitly adding them:

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions).
  addEnabledSecureTransportProtocol("TLSv1.1");
NetServer server = vertx.createNetServer(options);

They can also be removed:

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions).
  removeEnabledSecureTransportProtocol("TLSv1.2");
NetServer server = vertx.createNetServer(options);

Protocol versions can be specified on the NetServerOptions or NetClientOptions configuration.

TLS 1.0 (TLSv1) and TLS 1.1 (TLSv1.1) are widely deprecated and have been disabled by default since Vert.x 4.4.0.

SSL engine

The engine implementation can be configured to use OpenSSL instead of the JDK implementation. Before JDK started to use hardware intrinsics (CPU instructions) for AES in Java 8 and for RSA in Java 9, OpenSSL provided much better performances and CPU usage than the JDK engine.

The engine options to use is

NetServerOptions options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions);

// Use JDK SSL engine explicitly
options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions).
  setSslEngineOptions(new JdkSSLEngineOptions());

// Use OpenSSL engine
options = new NetServerOptions().
  setSsl(true).
  setKeyCertOptions(keyStoreOptions).
  setSslEngineOptions(new OpenSSLEngineOptions());

Server Name Indication (SNI)

Server Name Indication (SNI) is a TLS extension by which a client specifies a hostname attempting to connect: during the TLS handshake the client gives a server name and the server can use it to respond with a specific certificate for this server name instead of the default deployed certificate. If the server requires client authentication the server can use a specific trusted CA certificate depending on the indicated server name.

When SNI is active the server uses

  • the certificate CN or SAN DNS (Subject Alternative Name with DNS) to do an exact match, e.g www.example.com

  • the certificate CN or SAN DNS certificate to match a wildcard name, e.g *.example.com

  • otherwise the first certificate when the client does not present a server name or the presented server name cannot be matched

When the server additionally requires client authentication:

  • when JksOptions is set on trust options then an exact match with the trust store alias is done

  • otherwise the available CA certificates are used in the same way as if no SNI is in place

the server fails to bind with an error reporting an incorrect alias

You can enable SNI on the server by setting setSni to true and configured the server with multiple key/certificate pairs.

Java KeyStore files or PKCS12 files can store multiple key/cert pairs out of the box.

JksOptions keyCertOptions = new JksOptions().setPath("keystore.jks").setPassword("wibble");

NetServer netServer = vertx.createNetServer(new NetServerOptions()
    .setKeyCertOptions(keyCertOptions)
    .setSsl(true)
    .setSni(true)
);

PemKeyCertOptions can be configured to hold multiple entries:

PemKeyCertOptions keyCertOptions = new PemKeyCertOptions()
    .setKeyPaths(Arrays.asList("default-key.pem", "host1-key.pem", "etc..."))
    .setCertPaths(Arrays.asList("default-cert.pem", "host2-key.pem", "etc...")
    );

NetServer netServer = vertx.createNetServer(new NetServerOptions()
    .setKeyCertOptions(keyCertOptions)
    .setSsl(true)
    .setSni(true)
);

The client implicitly sends the connecting host as an SNI server name for Fully Qualified Domain Name (FQDN).

You can provide an explicit server name when connecting a socket

NetClient client = vertx.createNetClient(new NetClientOptions()
    .setTrustOptions(trustOptions)
    .setSsl(true)
);

// Connect to 'localhost' and present 'server.name' server name
client
  .connect(1234, "localhost", "server.name")
  .onComplete(res -> {
    if (res.succeeded()) {
      System.out.println("Connected!");
      NetSocket socket = res.result();
    } else {
      System.out.println("Failed to connect: " + res.cause().getMessage());
    }
  });

It can be used for different purposes:

  • present a server name different than the server host

  • present a server name while connecting to an IP

  • force to present a server name when using shortname

Application-Layer Protocol Negotiation (ALPN)

Application-Layer Protocol Negotiation (ALPN) is a TLS extension for application layer protocol negotiation. It is used by HTTP/2: during the TLS handshake the client gives the list of application protocols it accepts and the server responds with a protocol it supports.

Java TLS supports ALPN (Java 8 with the most recent versions).

OpenSSL ALPN support

OpenSSL also supports (native) ALPN.

OpenSSL requires to configure setSslEngineOptions and use netty-tcnative jar on the classpath. Using tcnative may require OpenSSL to be installed on your OS depending on the tcnative implementation.

Using a proxy for client connections

The NetClient supports either an HTTP/1.x CONNECT, SOCKS4a or SOCKS5 proxy.

The proxy can be configured in the NetClientOptions by setting a ProxyOptions object containing proxy type, hostname, port and optionally username and password.

Here’s an example:

NetClientOptions options = new NetClientOptions()
  .setProxyOptions(new ProxyOptions().setType(ProxyType.SOCKS5)
    .setHost("localhost").setPort(1080)
    .setUsername("username").setPassword("secret"));
NetClient client = vertx.createNetClient(options);

The DNS resolution is always done on the proxy server, to achieve the functionality of a SOCKS4 client, it is necessary to resolve the DNS address locally.

You can use setNonProxyHosts to configure a list of host bypassing the proxy. The lists accepts * wildcard for matching domains:

NetClientOptions options = new NetClientOptions()
  .setProxyOptions(new ProxyOptions().setType(ProxyType.SOCKS5)
    .setHost("localhost").setPort(1080)
    .setUsername("username").setPassword("secret"))
  .addNonProxyHost("*.foo.com")
  .addNonProxyHost("localhost");
NetClient client = vertx.createNetClient(options);

Using HA PROXY protocol

HA PROXY protocol provides a convenient way to safely transport connection information such as a client’s address across multiple layers of NAT or TCP proxies.

HA PROXY protocol can be enabled by setting the option setUseProxyProtocol and adding the following dependency in your classpath:

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-codec-haproxy</artifactId>
  <!--<version>Should align with netty version that Vert.x uses</version>-->
</dependency>
NetServerOptions options = new NetServerOptions().setUseProxyProtocol(true);
NetServer server = vertx.createNetServer(options);
server.connectHandler(so -> {
  // Print the actual client address provided by the HA proxy protocol instead of the proxy address
  System.out.println(so.remoteAddress());

  // Print the address of the proxy
  System.out.println(so.localAddress());
});