Server-Side HTTP/2 (Preview)

Warning

Server-Side HTTP/2 support in akka-http is currently available as a preview. This means it is ready to be evaluated, but the APIs and behavior are likely to change.

Dependency

To use Akka HTTP2 Support, add the module to your project:

sbt
libraryDependencies += "com.typesafe.akka" %% "akka-http2-support" % "10.1.15"
Gradle
def versions = [
  ScalaBinary: "2.12"
]
dependencies {
  implementation "com.typesafe.akka:akka-http2-support_${versions.ScalaBinary}:10.1.15"
}
Maven
<properties>
  <scala.binary.version>2.12</scala.binary.version>
</properties>
<dependencies>
  <dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http2-support_${scala.binary.version}</artifactId>
    <version>10.1.15</version>
  </dependency>
</dependencies>

Enable HTTP/2 support

HTTP/2 can then be enabled through configuration:

akka.http.server.preview.enable-http2 = on

Use bindAndHandleAsync and HTTPS

HTTP/2 is primarily used over a secure connection (known as “over HTTPS” or “with TLS”), which also takes care of protocol negotiation and falling back to plain HTTPS when the client does not support HTTP/2. See the HTTPS section for how to set up HTTPS.

You can use Http().bindAndHandleAsyncHttp().get(system).bindAndHandleAsync() as long as you followed the above steps:

Scala
sourceimport scala.concurrent.Future

import akka.http.scaladsl.HttpsConnectionContext
import akka.http.scaladsl.Http

Http().bindAndHandleAsync(
  asyncHandler,
  interface = "localhost",
  port = 8443,
  httpsServerContext)
Java
sourceimport akka.http.javadsl.Http;
import static akka.http.javadsl.ConnectHttp.toHostHttps;

Http.get(system)
  .bindAndHandleAsync(
    asyncHandler,
    toHostHttps("127.0.0.1", 8443).withCustomHttpsContext(httpsConnectionContext),
    materializer);

Note that bindAndHandle currently does not support HTTP/2, you must use bindAndHandleAsync.

HTTP/2 without HTTPS

While un-encrypted connections are allowed by HTTP/2, this is sometimes discouraged.

There are 2 ways to implement un-encrypted HTTP/2 connections: by using the HTTP Upgrade mechanism or by starting communication in HTTP/2 directly which requires the client to have Prior Knowledge of HTTP/2 support.

We support both approaches transparently on the same port. This feature is automatically enabled when HTTP/2 is enabled:

Scala
sourceimport akka.http.scaladsl.Http
import akka.http.scaladsl.HttpConnectionContext

Http().bindAndHandleAsync(
  asyncHandler,
  interface = "localhost",
  port = 8080,
  connectionContext = HttpConnectionContext())
Java
sourceimport static akka.http.javadsl.ConnectHttp.toHost;

Http.get(system)
  .bindAndHandleAsync(
    asyncHandler,
    toHost("127.0.0.1", 8080),
    materializer);

h2c Upgrade

The advantage of switching from HTTP/1.1 to HTTP/2 using the HTTP Upgrade mechanism is that both HTTP/1.1 and HTTP/2 clients can connect to the server on the same port, without being aware beforehand which protocol the server supports.

The disadvantage is that relatively few clients support switching to HTTP/2 in this way. Additionally, HTTP/2 communication cannot start until the first request has been completely sent. This means if your first request may be large, it might be worth it to start with an empty OPTIONS request to switch to HTTP/2 before sending your first ‘real’ request, at the cost of a roundtrip.

h2c with prior knowledge

The other option is to connect and start communicating in HTTP/2 immediately. The downside of this approach is the client must know beforehand that the server supports HTTP/2. For the reason this approach is known as h2c with Prior Knowledge](http://httpwg.org/specs/rfc7540.html#known-http) of HTTP/2 support.

Testing with cURL

At this point you should be able to connect, but HTTP/2 may still not be available.

You’ll need a recent version of cURL compiled with HTTP/2 support (for OSX see this article). You can check whether your version supports HTTP2 with curl --version, look for the nghttp2 extension and the HTTP2 feature:

curl 7.52.1 (x86_64-pc-linux-gnu) libcurl/7.52.1 OpenSSL/1.0.2l zlib/1.2.8 libidn2/0.16 libpsl/0.17.0 (+libidn2/0.16) libssh2/1.8.0 nghttp2/1.23.1 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

When you connect to your service you may now see something like:

$ curl -k -v https://localhost:8443
(...)
* ALPN, offering h2
* ALPN, offering http/1.1
(...)
* ALPN, server accepted to use h2
(...)
> GET / HTTP/1.1
(...)
< HTTP/2 200
(...)

If your curl output looks like above, you have successfully configured HTTP/2. However, on JDKs up to version 9, it is likely to look like this instead:

$ curl -k -v https://localhost:8443
(...)
* ALPN, offering h2
* ALPN, offering http/1.1
(...)
* ALPN, server did not agree to a protocol
(...)
> GET / HTTP/1.1
(...)
< HTTP/1.1 200 OK
(...)

This shows curl declaring it is ready to speak h2 (the shorthand name of HTTP/2), but could not determine whether the server is ready to, so it fell back to HTTP/1.1. To make this negotiation work you’ll have to configure ALPN as described below.

Application-Layer Protocol Negotiation (ALPN)

Application-Layer Protocol Negotiation (ALPN) is used to negotiate whether both client and server support HTTP/2.

ALPN support comes with the JVM starting from version 9 and in version 8 from update 252. If you’re on a previous version of the JVM, you’ll have to load a Java Agent to provide this functionality. We recommend to use a JVM >= 8u252. If you need to run from an older JVM, you need to use the agent from the Jetty project, jetty-alpn-agent, >= 2.0.10.

manually

This agent can be loaded with the -javaagent JVM option:

  java -javaagent:/path/to/jetty-alpn-agent-2.0.10.jar -jar app.jar

sbt

sbt can be configured to load the agent with the sbt-javaagent plugin:

  .enablePlugins(JavaAgent)
  .settings(
    javaAgents += "org.mortbay.jetty.alpn" % "jetty-alpn-agent" % "2.0.10" % "runtime"
  )

This should automatically load the agent when running, testing, or even in distributions made with sbt-native-package.

maven

To configure maven to load the agent when running mvn exec:exec, add it as a ‘runtime’ dependency:

<dependency>
    <groupId>org.mortbay.jetty.alpn</groupId>
    <artifactId>jetty-alpn-agent</artifactId>
    <version>2.0.10</version>
    <scope>runtime</scope>
</dependency>

and use the maven-dependency-plugin:

<plugin>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.5.1</version>
    <executions>
        <execution>
            <id>getClasspathFilenames</id>
            <goals>
                <goal>properties</goal>
            </goals>
        </execution>
     </executions>
</plugin>

to add it to the exec-maven-plugin arguments:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <configuration>
        <executable>java</executable>
        <arguments>
            <argument>-javaagent:${org.mortbay.jetty.alpn:jetty-alpn-agent:jar}</argument>
            <argument>-classpath</argument>
            <classpath />
            <argument>com.example.HttpServer</argument>
        </arguments>
    </configuration>
</plugin>
Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.