Using HTTP2 With Wildfly 9.0.0.Beta1

The upcoming Wildfly 9.0.0.Beta1 release supports HTTP2, which includes new features like request multiplexing and server push, unfortunately due to a few different factors it is not as straightforward to set up as HTTP1. The complexity comes because HTTP2 as implemented by major browsers requires the use of TLS. This is further complicated by the fact that it does not use straight TLS, but requires a extension called ALPN (application layer protocol negotiation).

Support for ALPN will be coming in Java 9, however unfortunately it is not present in JDK7 or JDK8. To get around this we need to install a jar that provides ALPN support into the JVM’s boot class path. The version of the jar file that you need is tied to the JVM version in use, so you must make sure you are using the correct version for you JVM.

Note that you must use JDK8 to use HTTP2, as HTTP2 requires stronger cyphers than are present in JDK7.

IMPORTANT: The script and instructions below are just using a self signed certificate that is present in the Undertow test suite to get you started. DO NOT USE THIS CERTIFICATE IN PRODUCTION. You should get a real certificate signed by a trusted certificate authority.

The TL;DR version

Make sure you are using JDK 1.8.0u40, then run the script below. This will (hopefully) perform all the steps that are required to setup Wildfly.

#!/bin/bash

if [ "x$JBOSS_HOME" == "x" ]; then
	echo "JBOSS_HOME is not set, please set it to the root of your Wildfly installation"
	exit
fi

cd $JBOSS_HOME

#IMPORTANT: The ALPN version changes depending on the version of the JVM you are using
#If you see class not found or similar SSL errors please look up the correct version
# at http://eclipse.org/jetty/documentation/current/alpn-chapter.html
ALPN_VERSION=8.1.3.v20150130

#download our fake certificate for testing
#DO NOT USE THIS IN PRODUCTION
#Get a real cert instead
curl https://raw.githubusercontent.com/undertow-io/undertow/master/core/src/test/resources/server.keystore >standalone/configuration/server.keystore
curl https://raw.githubusercontent.com/undertow-io/undertow/master/core/src/test/resources/server.truststore >standalone/configuration/server.truststore

#Download the ALPN jar we are interested in
curl http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/$ALPN_VERSION/alpn-boot-$ALPN_VERSION.jar >bin/alpn-boot-$ALPN_VERSION.jar

#Add ALPN to the boot class path
echo 'JAVA_OPTS="$JAVA_OPTS' " -Xbootclasspath/p:$JBOSS_HOME/bin/alpn-boot-$ALPN_VERSION.jar" '"' >>bin/standalone.conf

#Start Wildfly in the background
./bin/standalone.sh &
#wait for Wildfly to start
sleep 15

#Add a HTTPS connector
./bin/jboss-cli.sh -c "--command=/core-service=management/security-realm=https:add()"
./bin/jboss-cli.sh -c "--command=/core-service=management/security-realm=https/authentication=truststore:add(keystore-path=server.truststore, keystore-password=password, keystore-relative-to=jboss.server.config.dir)"
./bin/jboss-cli.sh -c "--command=/core-service=management/security-realm=https/server-identity=ssl:add(keystore-path=server.keystore, keystore-password=password, keystore-relative-to=jboss.server.config.dir)"
./bin/jboss-cli.sh -c "--command=/subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https, security-realm=https, enable-http2=true)"

#shut down Wildfly
kill `jps | grep jboss-modules.jar | cut -f1 -d ' ' `

A step by step guide

Figure out the correct Jetty ALPN Jar for your JDK version

Have a look at http://eclipse.org/jetty/documentation/current/alpn-chapter.html to figure out which version of Jetty ALPN is required for your JVM version. Download the correct jar version from maven central and place it in the Wildfly bin directory.

Add ALPN to the boot class path

Edit standalone.conf and add the following line to the JAVA_OPTS environment variable (making appropriate substitutions for the ALPN version). -Xbootclasspath/p:$JBOSS_HOME/bin/alpn-boot-$ALPN_VERSION.jar

Get a SSL certificate and create a keystore and truststore

For testing purposes you can download the ones from the Undertow test suite that are linked in the script above. For production use you will need to create a certificate and get it signed by a certificate authority. For the purposes of these instructions we assume they are located in standalone/configuration/server.[keystore|truststore].

Start Wildfly

We are going to make the remaining changes using the CLI, which means Wildfly must be started.

Connect to Wildfly using the CLI

Run the following command to connect: $JBOSS_HOME/bin/jboss-cli.sh -c.

Add a security realm

This contains the configuration that is used to tell the HTTPS connector which certificates to use.

/core-service=management/security-realm=https:add()
/core-service=management/security-realm=https/authentication=truststore:add(keystore-path=server.truststore, keystore-password=password, keystore-relative-to=jboss.server.config.dir)
/core-service=management/security-realm=https/server-identity=ssl:add(keystore-path=server.keystore, keystore-password=password, keystore-relative-to=jboss.server.config.dir)
Add a HTTPS connector
/subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https, security-realm=https, enable-http2=true)
Test it out

Fire up Chrome and head to https://localhost:8443. After clicking through the security warning about the self signed certificate you should see the normal Wildfly welcome page. Open up the developer tools and have a look in the Network tab. In the request headers section you should see chrome sending HTTP2 pseudo headers (:path, :authority, :method and :scheme). You can also look in the chrome://net-internals page, and you should be able to see the details of the HTTP2 session (although it will show up as a SPDY_SESSION event, as internally Chrome has been referring to HTTP2 as SPDY4).

Using Server Push with Undertow

The Undertow 1.2 branch supports both HTTP2 and SPDY, both of which support the concept of server push. If you are unfamiliar with server push this basically gives the server the ability to push resources cachable resources to a client before a client has requested them. For example if a client requests a HTML page and the server knows this page contains an image, the server could push the image to the client immediately, rather than waiting for the client to receive the page, parse the HTML and then send the request.

Caveats

Before I go into detail about how to use push, there are some pitfalls that you should be aware of. First you can only push resources that are allowed to be cached (think of pushed responses as pre-loading the browser cache).

You should also be aware that in general there is no reliable way to know if a browser already has a resource cached. If you push a resource that a browser has cached this is not a big deal, as the browser will just reset the stream, however it does waste some bandwidth and other server resources.

It is also important to note that if SPDY is in use then it is not possible to set the request headers that correspond to the pushed response, instead the relevant request headers from the current request are assumed by the browser.

Pushing is only allowed when a request is in progress, as all pushed responses correspond to an existing browser initiated request (you cannot do websocket style full duplex communication, at least not without some kind of long polling style hack).

The API

At the moment the API is experimental and somewhat subject to change. At the moment there is no standard API for server push (this is coming in Servlet 4.0), so it is necessary to use Undertow’s native API.

The Undertow API to support push is provided by the ServerConnection object, which can be obtained by calling HttpServerExchange.getConnection() (if you are using the Servlet API and don’t have access to the exchange you can get it by calling io.undertow.servlet.handlers.ServletRequestContext.requireCurrent().getExchange().

The API is as follows:


    /**
     * Attempts to push a resource if this connection supports server push. Otherwise the request is ignored.
     *
     * Note that push is always done on a best effort basis, even if this method returns true it is possible that
     * the remote endpoint will reset the stream.
     *
     * @param path The path of the resource
     * @param method The request method
     * @param requestHeaders The request headers
     * @return <code>true</code> if the server attempted the push, false otherwise
     */
    public boolean pushResource(final String path, final HttpString method, final HeaderMap requestHeaders);

    /**
     * Attempts to push a resource if this connection supports server push. Otherwise the request is ignored.
     *
     * Note that push is always done on a best effort basis, even if this method returns true it is possible that
     * the remote endpoint will reset the stream.
     *
     * The {@link io.undertow.server.HttpHandler} passed in will be used to generate the pushed response.
     *
     * @param path The path of the resource
     * @param method The request method
     * @param requestHeaders The request headers
     * @return <code>true</code> if the server attempted the push, false otherwise
     */
    public boolean pushResource(final String path, final HttpString method, final HeaderMap requestHeaders, HttpHandler handler);

    /**
     * Returns true if push is supported
     *
     * @return <code>true</code> if push is supported on this connection
     */
    public boolean isPushSupported() {
        return false;
    }

For the most part the API is fairly self explanatory, isPushSupported() can be used to determine if the connection supports push, and pushResource() is used to push the specified resource. Note that as specified above the requestHeaders parameter is ignored if SPDY is in use, it is only applicable for HTTP2.

The Learning Push Handler

Undertow provides a built in handler that provides some out of the box server push functionality. This handler is the io.undertow.server.handlers.LearningPushHandler. If you include this in your handler chain it will attempt to learn which resources browsers request soon after requesting specific pages, and uses this knowledge to initiate a server push.

This works by examining the referrer header on requests, and using a heuristic to decide which resources are a candidate to be pushed.

Undertow 1.1.0.Final has been released

Undertow 1.1.0.Final has been released, and is available from maven central. This release brings a number of improvements including:

  • Websockets 1.1

  • SPDY support

  • Many bug fixes

More details on websocket improvements and SPDY support will be coming in new blog posts in the coming weeks.

Undertow now supports HTTP2 Draft 15

The Undertow 1.2 branch now supports the latest draft of the HTTP2 spec. We have a demo site up and running at https://http2.undertow.io.

So far, we have verified HTTP/2 interop with the following browsers:

  • Chrome Canary (41.0.2217.0)

  • Firefox (Nightly build of 36.01a)

  • Internet Explorer (Windows 10 Preview)

The demo site contains instructions for testing it out, including getting it running locally. The HTTP2 code is still quite new, and will be evolving a lot over the coming months. For now most features of HTTP2 are supported, with the exception of priority, which is ignored.

Undertow 1.0.0.Beta1 Released

Undertow is a high performance non-blocking webserver that is the new web server in Wildfly.

Some notable features include:

  • HTTP upgrade support - This enables management and EJB invocations to be multiplexed over the web port without any performance loss

  • Websocket support, including JSR-356

  • Servlet 3.1 support

  • Ability to mix non-blocking handlers with Servlet

Undertow is completely standalone, starts in milliseconds, and has a fluent builder API for building deployments. This all makes it well suited for use in unit tests. An example of setting up an embedded servlet deployment is shown below:

One of the goals of Undertow was to have great performance and I am happy to say so far we have surpassed our own expectations. Against the competition Undertow comes out ahead on every internal test we have run, and this is also represented in a third party benchmark by Tech Empower (the benchmarks that were posted to the core a while back), where it comes in as the fastest Java web server (and in many of the tests as the fastest server overall).

The results are listed here:

If anyone want to participate the dev list is undertow-dev@lists.jboss.org and we hang out on #undertow on freenode.

Some examples

Embedded Servlet Deployment
DeploymentInfo servletBuilder = deployment()
        .setClassLoader(ServletServer.class.getClassLoader())
        .setContextPath("/myapp")
        .setDeploymentName("test.war")
        .addServlets(
                servlet("MessageServlet", MessageServlet.class)
                        .addInitParam("message", "Hello World")
                        .addMapping("/*"),
                servlet("MyServlet", MessageServlet.class)
                        .addInitParam("message", "MyServlet")
                        .addMapping("/myservlet"));

DeploymentManager manager = defaultContainer().addDeployment(servletBuilder);
manager.deploy();

Undertow server = Undertow.builder()
        .addListener(8080, "localhost")
        .setHandler(manager.start())
        .build();
server.start();
Simple non-blocking handler
Undertow server = Undertow.builder()
        .addListener(8080, "localhost")
        .setHandler(new HttpHandler() {
            @Override
            public void handleRequest(final HttpServerExchange exchange) throws Exception {
                exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                exchange.getResponseSender().send("Hello World");
            }
        }).build();
server.start();