java9+springboot2+undertow2 включает http2 и push сервера

задняя часть сервер Tomcat встроенный

последовательность

В этой статье в основном рассматриваются java9+springboot2+undertow2 для включения http2 и 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 для удаления зависимости от tomcat в starter-web.

настроить

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

экземпляр генерации хранилища ключей

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 и 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.

сервер push-экземпляр

мавен изменения

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

Поскольку для серверного push требуется версия servlet4, версия undertow, от которой зависит springboot2, по-прежнему является версией 1.4, это только servlet3, поэтому необходимо дополнительно исключить, а затем ввести версию 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());
    }    

бегать

  • не включать сервер push

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

Я не использовал push-уведомление сервера, в колонке Initiator я увидел, что /demo было запущено. Нажмите на водопад, вы увидите отнимающую много времени загрузку контента.

  • включить отправку сервера

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

Видно, что если вы используете push сервера, в колонке Initiator есть логотип Push, нажмите на водопад, вы увидите, сколько времени занимает чтение push.

резюме

Поскольку java9 поддерживает HTTP2, а servlet4 представляет PushBuilder для поддержки серверной загрузки, разработчики, использующие java в качестве языка разработки на стороне сервера, могут легче практиковать HTTP2.

На момент написания этой статьи servlet4 поддерживал несколько основных контейнеров сервлетов:

  • jetty еще не выпустила версию реализации, поддерживающую servlet4;
  • Tomcat имеет версию 9.x, которая поддерживает servlet4, но замена зависимостей на springboot2 сообщает об ошибках, что в целом на практике немного проблематично;
  • Версия undertow2.0.1.Final поддерживает servlet 4 и заменяет зависимости от springboot 2. Это очень просто и об ошибках не сообщается, поэтому в этой статье выбран вариант undertow.

doc