последовательность
В этой статье в основном рассматриваются 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
Я не использовал push-уведомление сервера, в колонке Initiator я увидел, что /demo было запущено. Нажмите на водопад, вы увидите отнимающую много времени загрузку контента.
- включить отправку сервера
Видно, что если вы используете 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.