Enable HTTP2 with Tomcat in Spring Boot
Asked Answered
C

5

56

Tomcat 8.5, which will be the default in Spring Boot 1.4, supports HTTP/2.

How can HTTP/2 be enabled in a Spring Boot application?

Camera answered 27/7, 2016 at 12:16 Comment(0)
C
27

The most elegant and best-performing way to enable HTTP/2 with a Spring Boot application follows here.

First, as mentioned in Andy Wilkinson's answer, you need to enable HTTP/2 at Tomcat level:

@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
    return (container) -> {
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            ((TomcatEmbeddedServletContainerFactory) container)
                    .addConnectorCustomizers((connector) -> {
                connector.addUpgradeProtocol(new Http2Protocol());
            });
        }
    };
}

In case you are not using an embedded Tomcat, you can set up HTTP/2 listening like this:

<Connector port="5080" protocol="HTTP/1.1" connectionTimeout="20000">
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
</Connector>

Remember that you need Tomcat >= 8.5.

Then, you should use HAProxy (version >= 1.7) in front of Tomcat to take care of encryption.

The client will speak https to HAProxy, and HAProxy will speak cleartext HTTP/1.1 or HTTP/2 to the backend, as the client requested. There will be no unnecessary protocol translations.

The matching HAProxy-configuration is here:

# Create PEM: cat cert.crt cert.key ca.crt > /etc/ssl/certs/cert.pem

global
    tune.ssl.default-dh-param 2048
    ssl-default-bind-options no-sslv3 no-tls-tickets force-tlsv12
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    chroot /var/lib/haproxy
    user haproxy
    group haproxy

defaults
    timeout connect 10000ms
    timeout client 60000ms
    timeout server 60000ms

frontend fe_https
    mode tcp
    rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload
    rspadd X-Frame-Options:\ DENY
    bind *:443 ssl crt /etc/ssl/certs/cert.pem alpn h2,http/1.1
    default_backend be_http

backend be_http
    mode tcp
    server domain 127.0.0.1:8080
    # compression algo gzip # does not work in mode "tcp"
    # compression type text/html text/css text/javascript application/json

Edit 2019

I face two problems when using mode "tcp"

  • Compression does not work, since it depends on mode http. So the backend has to take care of it
  • The backend can not see the client's IP-address. Probably I need NAT. Still investigating...

Generally, since haproxy proxies a lower level tcp connection, there is no access to any http stuff

Camera answered 4/4, 2017 at 12:43 Comment(6)
Is there a way to not use HAproxy? Spring boot and tomcat couldn't just handle encription and the upgrade?Froth
Check this link readlearncode.com/configure-tomcat-9-for-http2 posted by Alex above.Camera
I was planning to use it with embedded tomcat of spring which is 8.5. I think I will try Undertow it is supposed to have support for http2Froth
Embedded Tomcat does also support http2, and ssl can be configured like this: docs.spring.io/spring-boot/docs/current/reference/html/…. If you do that, and add the connector.addUpgradeProtocol(new Http2Protocol()); I believed it should work.Camera
Do you have source about embedded tomcat (8.5) support of http2? I read something about tomcat native. I havent been able to set up tomcat yet.Froth
I have checked with spring boot 2.2.4, embedded tomcat 9.x and JAVA8. By configuring server.http2.enabled=true and setting up the java.library.path to tomcat native bin path, I am able to see the APIs running on HTTP2.Halflife
D
64

In Spring Boot 2.1 and above it is as simple as adding this property to your .properties (or .yml) file:

server.http2.enabled=true

You can also do it programmatically like this (in one of your configuration classes):

@Bean
public ConfigurableServletWebServerFactory tomcatCustomizer() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addConnectorCustomizers(connector -> connector.addUpgradeProtocol(new Http2Protocol()));
    return factory;
}
Drumlin answered 16/5, 2018 at 7:56 Comment(2)
this only works when JDK 9 or above is used. I have been trying to enable it for my spring boot service running in a docker container but it does not work . Check here docs.spring.io/spring-boot/docs/current/reference/html/…Hildebrandt
It just enables HTTP2, but any configuration in application.yml is not passed to Http2Protocol. I could not find any other solution than java self implemented config, that forwards server.* properties to Http2Protocol.Novotny
B
37

You need to add the HTTP 2 upgrade protocol to Tomcat's connector. You can do that by customizing the embedded Tomcat container:

Java 8:

@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
    return (container) -> {
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            ((TomcatEmbeddedServletContainerFactory) container)
                    .addConnectorCustomizers((connector) -> {
                connector.addUpgradeProtocol(new Http2Protocol());
            });
        }
    };
}

Java 7:

@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
    return new EmbeddedServletContainerCustomizer() {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            if (container instanceof TomcatEmbeddedServletContainerFactory) {
                ((TomcatEmbeddedServletContainerFactory) container)
                        .addConnectorCustomizers(new TomcatConnectorCustomizer() {
                    @Override
                    public void customize(Connector connector) {
                        connector.addUpgradeProtocol(new Http2Protocol());
                    }

                });
            }
        }

    };
}
Bellringer answered 27/7, 2016 at 12:55 Comment(8)
Is there anything else that needs to be done apart of running Tomcat with https? (I did enable it for localhost by following this question #30405079). But in Chrome's network debugger I see everything is still served via http/1.1Camera
h2c will work as-is but not many (any?) browsers support it. If you want it to work securely (h2), you'll need to jump through some hoops with Tomcat Native.Bellringer
That's a strange way to ask for more help. I'm done here.Bellringer
Just to add that Tomcat 9 supports HTTP2 with a few configuration in the server.xml file. readlearncode.com/configure-tomcat-9-for-http2Valdavaldas
I have a potentially very dumb question. Several times, I have seen "include this bean" in relation to Spring Boot ... but I have not been able to figure out what file to open/create before I paste the bean.Algarroba
Just add snippet above in a class annotated with @Configuration of spring frameworkClinometer
@AndyWilkinson i tried your solution and subsequently added a new TLS 1.2 self signed certificate but still my browser is using http 1.1. Is there a problem with embedded tomcat?Carolann
I can’t say without some more information. Why not add a question of your own, providing as much information as you can?Bellringer
C
27

The most elegant and best-performing way to enable HTTP/2 with a Spring Boot application follows here.

First, as mentioned in Andy Wilkinson's answer, you need to enable HTTP/2 at Tomcat level:

@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
    return (container) -> {
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            ((TomcatEmbeddedServletContainerFactory) container)
                    .addConnectorCustomizers((connector) -> {
                connector.addUpgradeProtocol(new Http2Protocol());
            });
        }
    };
}

In case you are not using an embedded Tomcat, you can set up HTTP/2 listening like this:

<Connector port="5080" protocol="HTTP/1.1" connectionTimeout="20000">
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
</Connector>

Remember that you need Tomcat >= 8.5.

Then, you should use HAProxy (version >= 1.7) in front of Tomcat to take care of encryption.

The client will speak https to HAProxy, and HAProxy will speak cleartext HTTP/1.1 or HTTP/2 to the backend, as the client requested. There will be no unnecessary protocol translations.

The matching HAProxy-configuration is here:

# Create PEM: cat cert.crt cert.key ca.crt > /etc/ssl/certs/cert.pem

global
    tune.ssl.default-dh-param 2048
    ssl-default-bind-options no-sslv3 no-tls-tickets force-tlsv12
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
    chroot /var/lib/haproxy
    user haproxy
    group haproxy

defaults
    timeout connect 10000ms
    timeout client 60000ms
    timeout server 60000ms

frontend fe_https
    mode tcp
    rspadd Strict-Transport-Security:\ max-age=31536000;\ includeSubDomains;\ preload
    rspadd X-Frame-Options:\ DENY
    bind *:443 ssl crt /etc/ssl/certs/cert.pem alpn h2,http/1.1
    default_backend be_http

backend be_http
    mode tcp
    server domain 127.0.0.1:8080
    # compression algo gzip # does not work in mode "tcp"
    # compression type text/html text/css text/javascript application/json

Edit 2019

I face two problems when using mode "tcp"

  • Compression does not work, since it depends on mode http. So the backend has to take care of it
  • The backend can not see the client's IP-address. Probably I need NAT. Still investigating...

Generally, since haproxy proxies a lower level tcp connection, there is no access to any http stuff

Camera answered 4/4, 2017 at 12:43 Comment(6)
Is there a way to not use HAproxy? Spring boot and tomcat couldn't just handle encription and the upgrade?Froth
Check this link readlearncode.com/configure-tomcat-9-for-http2 posted by Alex above.Camera
I was planning to use it with embedded tomcat of spring which is 8.5. I think I will try Undertow it is supposed to have support for http2Froth
Embedded Tomcat does also support http2, and ssl can be configured like this: docs.spring.io/spring-boot/docs/current/reference/html/…. If you do that, and add the connector.addUpgradeProtocol(new Http2Protocol()); I believed it should work.Camera
Do you have source about embedded tomcat (8.5) support of http2? I read something about tomcat native. I havent been able to set up tomcat yet.Froth
I have checked with spring boot 2.2.4, embedded tomcat 9.x and JAVA8. By configuring server.http2.enabled=true and setting up the java.library.path to tomcat native bin path, I am able to see the APIs running on HTTP2.Halflife
K
15

In Spring Boot 2 you first need a certificate - it can by generated like this:

keytool -genkey -keyalg RSA -alias my-the-best-api -keystore c:\tmp\keystore.store -storepass secret -validity 3650 -keysize 2048

Than you just need to add this certificate to classpath and add needed properties to application.properties:

server.http2.enabled=true
server.port = 8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
Kaylil answered 12/9, 2018 at 15:20 Comment(1)
Worth noting that Spring Boot 2.0.x uses Tomcat 8.5, so if using embedded Tomcat, you still need to configure the path to libtcnative as noted in the Spring Boot docs. Spring Boot 2.1.x will ship with Tomcat 9.Public
I
7

Spring Boot 2.2.0+ ships by default with Tomcat 9.0.x which supports HTTP/2 out of the box when using JDK 9 or later. Link

Insomuch answered 30/5, 2020 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.