java9+springboot2+undertow2啟用http2及server push

go4it發表於2018-03-10

本文主要研究下java9+springboot2+undertow2啟用http2及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>
複製程式碼

注意這裡使用undertow,移除掉了starter-web中的tomcat依賴

配置

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生成例項

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

ENABLE_HTTP2及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以及server push功能

HTTP2例項

controller

@RestController
public class IndexController {

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

執行

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
複製程式碼

注意,這裡curl命令使用-k引數忽略校驗https,否則報錯

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例項

maven改動

	<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>
複製程式碼

由於server push需要servlet4版本,目前springboot2依賴的undertow還是1.4版本的還只是servlet3,因此這裡需要額外exclude掉再引入undertow2版本以支援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());
    }    
複製程式碼

執行

  • 不啟用server push

螢幕快照 2018-03-10 下午3.19.49.png

沒有用server push,在在Initiator那欄,看到的是/demo這個觸發的。點開waterfall,會看到Content Download的耗時。

  • 啟用server push

螢幕快照 2018-03-10 下午3.18.45.png

可以看到如果是用server push的,在Initiator那欄,有個Push標識,點開waterfall,會看到reading push的耗時。

小結

隨著java9支援HTTP2,servlet4引入PushBuilder支援server push,使用java作為服務端開發語言的開發者可以更方便地將HTTP2實踐起來。

截止到寫這篇文章之時,幾大servlet容器的servlet4支援情況:

  • jetty尚且沒有看到支援servlet4的實現版本釋出;
  • tomcat有9.x版本支援servlet4,但是在springboot2上替換依賴報錯,整體實踐起來稍稍麻煩;
  • undertow2.0.1.Final版本支援servlet4,在springboot2上替換依賴,非常簡單,沒有報錯,這也是本文選擇undertow的原因。

doc

相關文章