Java 9+springboot2+untertow2 enables http2 and server push

  http-2

Order

This paper mainly studies how java9+springboot2+undertow2 enables http2 and server push.

maven

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>9</java.version>
    </properties>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

Note the use of undertow here, removing tomcat dependency from starter-web

Configuration

application.yml

server:
  port: 8443
  ssl:
    key-store: classpath:keystore.jks
    key-store-password: xxx
    key-password: xxx
    protocol: TLSv1.2
  http2:
    enabled: true
  use-forward-headers: true

Keystore build instance

keytool -genkey -keyalg RSA -alias selfsigned -keystore src/main/resources/keystore.jks -storepass xxx -validity 360 -keysize 2048

ENABLE_HTTP2 and ENABLE_PUSH

@Configuration
public class Http2Config {

    @Bean
    UndertowServletWebServerFactory undertowServletWebServerFactory() {
        UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
        factory.addBuilderCustomizers(
                builder -> {
                    builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true)
                            .setServerOption(UndertowOptions.HTTP2_SETTINGS_ENABLE_PUSH,true);
                });

        return factory;
    }
}

HTTP2 and server push functions are turned on here.

HTTP2 instance

controller

@RestController
public class IndexController {

    /**
     * curl -Ik --http2 https://localhost:8443/hello
     * @return
     */
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}    

run

curl -Ivk --http2 https://localhost:8443/hello
*   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-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*      subject: C=ZH; ST=guangdong; L=shenzhen; O=spring; OU=springboot; CN=localhost
*      start date: Mar  9 14:10:54 2018 GMT
*      expire date: Mar  4 14:10:54 2019 GMT
*      issuer: C=ZH; ST=guangdong; L=shenzhen; O=spring; OU=springboot; 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 0x7f8a5280ea00)
> HEAD /hello HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/2.0 200
HTTP/2.0 200
< content-type:text/plain;charset=UTF-8
content-type:text/plain;charset=UTF-8
< content-length:5
content-length:5
< date:Fri, 09 Mar 2018 15:18:36 GMT
date:Fri, 09 Mar 2018 15:18:36 GMT

<
* Connection #0 to host localhost left intact

Note that the curl command here uses the -k parameter to ignore checking https, otherwise an error is reported

curl -I --http2 https://localhost:8443/hello
curl: (60) SSL certificate problem: self signed certificate
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Server push instance

Maven changes

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>9</java.version>
        <servlet-api.version>4.0.0</servlet-api.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-core</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-servlet</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-websockets-jsr</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-servlet</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-websockets-jsr</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

Because server push requires servlet4 version, currently springboot2 is only relying on servlet3 in version 1.4 of undertow, so it is necessary to add extra exclude and then introduce the version of undertow2 to support servelt4.

controller

    @GetMapping("/demo")
    public void http2ServerPush(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PushBuilder pushBuilder = request.newPushBuilder();
        pushBuilder
                .path("/demo.png")
                .addHeader("content-type", "image/png")
                .push();
        try(PrintWriter respWriter = response.getWriter()){
            respWriter.write("<html>" +
                            "<img src='/demo.png'>" +
                    "</html>");
        }
    }
   
    @GetMapping(value = "/demo.png")
    public void download(HttpServletResponse response) throws IOException {
        InputStream data = getClass().getClassLoader().getResourceAsStream("demo.png");
        response.setHeader("content-type", "image/png");
        FileCopyUtils.copy(data,response.getOutputStream());
    }    

run

  • Server push is not enabled

图片描述

There is no server push, in the Initiator column, see is /demo this trigger. Click on waterfall and you will see the time-consuming Content Download.

  • Enable server push

图片描述

It can be seen that if the server Push is used, there is a push mark in the Initiator column. if you click on waterfall, you will see the time consuming of reading push.

Summary

With java9 supporting HTTP2 and servlet4 introducing PushBuilder to support server push, developers using java as a server-side development language can more easily put HTTP2 into practice.

As of this writing, servlet4 support for several major servlet containers:

  • Jetty has yet to see the release of an implementation version that supports servlet4;
  • Tomcat supports servlet4 in version 9.x, but replacing dependencies on springboot2 reports errors, which makes overall practice a little cumbersome.
  • Undertow undertow2.0.1.Final supports servlet4 and replaces dependencies on springboot2. It is very simple and does not report errors, which is why this article chose Undertow.

doc