Setting up Vert.x HTTP/2 with JKS

I was playing with Vert.x’s excellent HTTP/2 functionality with the aim of determining how difficult it’d be to add support into apiman's Vert.x gateway implementation (which is still somewhat of a tech preview, but it’s finally getting some love). Many HTTP/2 clients don’t support non-TLS connections, so transport security is a de facto requirement in the real-world usage (particularly with browsers).

There’s already an example in the Vert.x core-examples repository demonstrating how to set up and use HTTP/2. However, when using a keystore in JKS format, Vert.x (in v3.3.2, at least) tries to use Jetty’s alpn-boot ALPN implementation rather than OpenSSL.

A few simple steps…​

So, here’s how to go about making it work:

  1. Download the version of apln-boot corresponding to your version of Java. The binaries are available on Maven Central, or you can build your own if you prefer.

  2. When launching your verticle, put alpn-boot on the JVM’s boot classpath; it’s altering core library functionality and needs to be available early. There are a few ways to do this, here’s the easiest one:

    $ JAVA_OPTS=-Xbootclasspath/p:<path-to-apln-boot.jar> vertx run Example.java

    If you’re using an IDE you should add -Xbootclasspath/p:<path-to-apln-boot.jar> to the VM Arguments of your run configuration.

  3. Set setUseAlpn(true) on your HttpServerOptions.

Testing it out

Here’s some code to test it with.

import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.net.JksOptions;

public class Example extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        HttpServer server =
        vertx.createHttpServer(new HttpServerOptions()
            .setUseAlpn(true)
            .setSsl(true)
            .setKeyStoreOptions(
                new JksOptions()
                .setPath(<Path to your JKS>)
                .setPassword(<The JKS password>)
            )
        );

        server.requestHandler(req -> {
            req.response().putHeader("Content-Type", "text/plain")
                .end("Greetings, Earthicans!\n");
        }).listen(8443);
    }
}

You’ll likely want to check that it’s really working. I’ve been using cURL compiled with nghttp2 to enable HTTP/2 support; depending on your OS or distro, the shipping version may have been built with it. If you’re using OS X, you can use brew install curl --with-nghttp2. [1]

 $  curl -k -v --http2 https://localhost:8443/
 *   Trying ::1...
 * Connected to localhost (::1) port 8443 (#0)
 * ALPN, offering h2
 * ALPN, offering http/1.1
 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
 * successfully set certificate verify locations:
 *   CAfile: /usr/local/etc/openssl/cert.pem
   CApath: none
 * TLSv1.2 (OUT), TLS header, Certificate Status (22):
 * TLSv1.2 (OUT), TLS handshake, Client hello (1):
 * TLSv1.2 (IN), TLS handshake, Server hello (2):
 * TLSv1.2 (IN), TLS handshake, Certificate (11):
 * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
 * TLSv1.2 (IN), TLS handshake, Server finished (14):
 * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
 * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
 * TLSv1.2 (OUT), TLS handshake, Finished (20):
 * TLSv1.2 (IN), TLS change cipher, Client hello (1):
 * TLSv1.2 (IN), TLS handshake, Finished (20):
 * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
 * ALPN, server accepted to use h2
 * Server certificate:
 *  subject: C=lo; ST=localhost; L=localhost; O=localhost; OU=localhost; CN=localhost
 *  start date: Aug 20 15:23:02 2016 GMT
 *  expire date: Nov 18 15:23:02 2016 GMT
 *  issuer: C=lo; ST=localhost; L=localhost; O=localhost; OU=localhost; CN=localhost
 *  SSL certificate verify result: self signed certificate (18), continuing anyway.
 * Using HTTP2, server supports multi-use
 * Connection state changed (HTTP/2 confirmed)
 * TCP_NODELAY set
 * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
 * Using Stream ID: 1 (easy handle 0x7fb8aa008800)
 > GET / HTTP/1.1
 > Host: localhost:8443
 > User-Agent: curl/7.50.1
 > Accept: */*
 >
 * Connection state changed (MAX_CONCURRENT_STREAMS updated)!
 < HTTP/2 200
 < content-type: text/plain
 < content-length: 22
 <
 * Connection #0 to host localhost left intact
 Greetings, Earthicans!

Hooray!

Vert.x really is a gratifyingly well-thought-out and easy-to-use.


1. But remember cURL is not linked by default so you’ll have to dig into its Cellar (e.g. /usr/local/Cellar/curl/7.50.1/bin/curl). Of course, you could alias it, or force link :-).
comments powered by Disqus