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:

value 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:

value options = NetServerOptions {
  port = 4321;
};
value 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:

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

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

value 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:

value server = vertx.createNetServer();
server.listen(1234, "localhost", (NetServer|Throwable res) => {
  if (is NetServer res) {
    print("Server is now listening!");
  } else {
    print("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.

value server = vertx.createNetServer();
server.listen(0, "localhost", (NetServer|Throwable res) => {
  if (is NetServer res) {
    print("Server is now listening on actual port: ``server.actualPort()``");
  } else {
    print("Failed to bind!");
  };
});

Getting notified of incoming connections

To be notified when a connection is made you need to set a `link:../../ceylondoc/vertx-core//net/NetServer.type.html#connectHandler((@io.vertx.codegen.annotations.Nullable

io.vertx.core.Handler))[connectHandler]`:

value server = vertx.createNetServer();
server.connectHandler((NetSocket 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.

value server = vertx.createNetServer();
server.connectHandler((NetSocket socket) => {
  socket.handler((Buffer buffer) => {
    print("I received some bytes: ``buffer.length()``");
  });
});

Writing data to a socket

You write to a socket using one of write.

// Write a buffer
value buffer = buffer.buffer().appendFloat(12.34).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 `link:../../ceylondoc/vertx-core//net/NetSocket.type.html#closeHandler((@io.vertx.codegen.annotations.Nullable

io.vertx.core.Handler))[closeHandler]` on it:

socket.closeHandler((Void v) => {
  print("The socket has been closed");
});

Handling exceptions

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

Event bus write handler

Every socket automatically registers a handler on the event bus, and when any buffers are received in this handler, it writes them to itself.

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

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

Files 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.

socket.sendFile("myfile.dat");

Streaming sockets

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

See the chapter on streams and pumps 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.

Closing a TCP Server

Call close to close the server. Closing the server closes any open connections and releases all server resources.

The close is actually asynchronous and might not complete until some time after the call has returned. If you want to be notified when the actual close has completed then you can pass in a handler.

This handler will then be called when the close has fully completed.

server.close((Throwable? res) => {
  if (!exists res) {
    print("Server is now closed");
  } else {
    print("close failed");
  };
});

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:

// Create a few instances so we can utilise cores

variable value i = 0;
while (i < 10) {
  value server = vertx.createNetServer();
  server.connectHandler((NetSocket socket) => {
    socket.handler((Buffer buffer) => {
      // Just echo back the data
      socket.write(buffer);
    });
  });
  server.listen(1234, "localhost");
  i++};

or, if you are using verticles you can simply deploy more instances of your server verticle by using the -instances option on the command line:

vertx run com.mycompany.MyVerticle -instances 10

or when programmatically deploying your verticle

value options = DeploymentOptions {
  instances = 10;
};
vertx.deployVerticle("com.mycompany.MyVerticle", options);

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:

value 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:

value options = NetClientOptions {
  connectTimeout = 10000;
};
value 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.

value options = NetClientOptions {
  connectTimeout = 10000;
};
value client = vertx.createNetClient(options);
client.connect(4321, "localhost", (NetSocket|Throwable res) => {
  if (is NetSocket res) {
    print("Connected!");
    value socket = res;
  } else {
    print("Failed to connect: ``res.getMessage()``");
  };
});

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 reconnectInterval and reconnectAttempts.

Note
Currently Vert.x will not attempt to reconnect if a connection fails, reconnect attempts and interval only apply to creating initial connections.
value options = NetClientOptions {
  reconnectAttempts = 10;
  reconnectInterval = 500;
};

value client = vertx.createNetClient(options);

By default, multiple connection attempts are disabled.

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 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:

value options = NetServerOptions {
  ssl = true;
  keyStoreOptions = JksOptions {
    path = "/path/to/your/server-keystore.jks";
    password = "password-of-your-keystore";
  };
};
value server = vertx.createNetServer(options);

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

value myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.jks");
value jksOptions = JksOptions {
  value = myKeyStoreAsABuffer;
  password = "password-of-your-keystore";
};
value options = NetServerOptions {
  ssl = true;
  keyStoreOptions = jksOptions;
};
value 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:

value options = NetServerOptions {
  ssl = true;
  pfxKeyCertOptions = PfxOptions {
    path = "/path/to/your/server-keystore.pfx";
    password = "password-of-your-keystore";
  };
};
value server = vertx.createNetServer(options);

Buffer configuration is also supported:

value myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.pfx");
value pfxOptions = PfxOptions {
  value = myKeyStoreAsABuffer;
  password = "password-of-your-keystore";
};
value options = NetServerOptions {
  ssl = true;
  pfxKeyCertOptions = pfxOptions;
};
value server = vertx.createNetServer(options);

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

value options = NetServerOptions {
  ssl = true;
  pemKeyCertOptions = PemKeyCertOptions {
    keyPath = "/path/to/your/server-key.pem";
    certPath = "/path/to/your/server-cert.pem";
  };
};
value server = vertx.createNetServer(options);

Buffer configuration is also supported:

value myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-key.pem");
value myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-cert.pem");
value pemOptions = PemKeyCertOptions {
  keyValue = myKeyAsABuffer;
  certValue = myCertAsABuffer;
};
value options = NetServerOptions {
  ssl = true;
  pemKeyCertOptions = pemOptions;
};
value server = vertx.createNetServer(options);

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

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:

value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  trustStoreOptions = JksOptions {
    path = "/path/to/your/truststore.jks";
    password = "password-of-your-truststore";
  };
};
value server = vertx.createNetServer(options);

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

value myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  trustStoreOptions = JksOptions {
    value = myTrustStoreAsABuffer;
    password = "password-of-your-truststore";
  };
};
value 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:

value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  pfxTrustOptions = PfxOptions {
    path = "/path/to/your/truststore.pfx";
    password = "password-of-your-truststore";
  };
};
value server = vertx.createNetServer(options);

Buffer configuration is also supported:

value myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  pfxTrustOptions = PfxOptions {
    value = myTrustStoreAsABuffer;
    password = "password-of-your-truststore";
  };
};
value server = vertx.createNetServer(options);

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

value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  pemTrustOptions = PemTrustOptions {
    certPaths = {"/path/to/your/server-ca.pem" };
  };
};
value server = vertx.createNetServer(options);

Buffer configuration is also supported:

value myCaAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-ca.pfx");
value options = NetServerOptions {
  ssl = true;
  clientAuth = required;
  pemTrustOptions = PemTrustOptions {
    certValues = {myCaAsABuffer };
  };
};
value 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.

value options = NetClientOptions {
  ssl = true;
  trustAll = true;
};
value 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.

Likewise 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.

value options = NetClientOptions {
  ssl = true;
  trustStoreOptions = JksOptions {
    path = "/path/to/your/truststore.jks";
    password = "password-of-your-truststore";
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
value options = NetClientOptions {
  ssl = true;
  trustStoreOptions = JksOptions {
    value = myTrustStoreAsABuffer;
    password = "password-of-your-truststore";
  };
};
value 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:

value options = NetClientOptions {
  ssl = true;
  pfxTrustOptions = PfxOptions {
    path = "/path/to/your/truststore.pfx";
    password = "password-of-your-truststore";
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
value options = NetClientOptions {
  ssl = true;
  pfxTrustOptions = PfxOptions {
    value = myTrustStoreAsABuffer;
    password = "password-of-your-truststore";
  };
};
value client = vertx.createNetClient(options);

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

value options = NetClientOptions {
  ssl = true;
  pemTrustOptions = PemTrustOptions {
    certPaths = {"/path/to/your/ca-cert.pem" };
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/ca-cert.pem");
value options = NetClientOptions {
  ssl = true;
  pemTrustOptions = PemTrustOptions {
    certValues = {myTrustStoreAsABuffer };
  };
};
value 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.

value options = NetClientOptions {
  ssl = true;
  keyStoreOptions = JksOptions {
    path = "/path/to/your/client-keystore.jks";
    password = "password-of-your-keystore";
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.jks");
value jksOptions = JksOptions {
  value = myKeyStoreAsABuffer;
  password = "password-of-your-keystore";
};
value options = NetClientOptions {
  ssl = true;
  keyStoreOptions = jksOptions;
};
value 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:

value options = NetClientOptions {
  ssl = true;
  pfxKeyCertOptions = PfxOptions {
    path = "/path/to/your/client-keystore.pfx";
    password = "password-of-your-keystore";
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.pfx");
value pfxOptions = PfxOptions {
  value = myKeyStoreAsABuffer;
  password = "password-of-your-keystore";
};
value options = NetClientOptions {
  ssl = true;
  pfxKeyCertOptions = pfxOptions;
};
value client = vertx.createNetClient(options);

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

value options = NetClientOptions {
  ssl = true;
  pemKeyCertOptions = PemKeyCertOptions {
    keyPath = "/path/to/your/client-key.pem";
    certPath = "/path/to/your/client-cert.pem";
  };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-key.pem");
value myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-cert.pem");
value pemOptions = PemKeyCertOptions {
  keyValue = myKeyAsABuffer;
  certValue = myCertAsABuffer;
};
value options = NetClientOptions {
  ssl = true;
  pemKeyCertOptions = pemOptions;
};
value client = vertx.createNetClient(options);

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

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:

value options = NetClientOptions {
  ssl = true;
  trustStoreOptions = trustOptions;
  crlPaths = {"/path/to/your/crl.pem" };
};
value client = vertx.createNetClient(options);

Buffer configuration is also supported:

value myCrlAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/crl.pem");
value options = NetClientOptions {
  ssl = true;
  trustStoreOptions = trustOptions;
  crlValues = {myCrlAsABuffer };
};
value client = vertx.createNetClient(options);

Configuring the Cipher suite

By default, the TLS configuration will uses the Cipher suite of the JVM running Vert.x. This Cipher suite can be configured with a suite of enabled ciphers:

value options = NetServerOptions {
  ssl = true;
  keyStoreOptions = keyStoreOptions;
  enabledCipherSuites = {"ECDHE-RSA-AES128-GCM-SHA256" , "ECDHE-ECDSA-AES128-GCM-SHA256" , "ECDHE-RSA-AES256-GCM-SHA384" , "CDHE-ECDSA-AES256-GCM-SHA384" };
};
value server = vertx.createNetServer(options);

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