jetty 和 springboot 应用如何从http1.x升级至http2
一、 why http2 (引用)
a)http2 相比于http1.x 有以下优点:
-
二进制格式:非明文协议,将数据分为数据帧,更利于组织和传输;
-
多路复用:允许使用单个连接同时发起多个请求,不受数量限制;
-
请求优先级:高优先级的请求可以更快地获得相应;
-
流量控制:类似 TCP 的流量控制机制,使用 “窗口” 避免拥塞;
-
头部压缩:使用专用的 HPACK 算法压缩冗余的头部信息;
-
支持服务端推送:服务器可以主动向客户端发送 “可能” 需要的资源;
-
强安全性:禁用了数百种不再安全的算法,减少了被攻破的可能;
-
非强制加密:允许用户在安全与性能间做出自己的选择。
b)http2 同 http1.x 的性能对比,参考链接
c)http2 的请求示例
curl -i --http2 https://cn.bing.com
HTTP/2 200
cache-control: private, max-age=0
content-length: 112737
content-type: text/html; charset=utf-8
p3p: CP="NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND"
d)http2 with ALPN (引用)
The development of new web protocols such as HTTP/2 raised the need of protocol negotiation within a Transport Layer Security (TLS) handshake. A protocol negotiation called ALPN (Application Layer Protocol Negotiation - RFC7301) has been defined to accomplish this.
HTTP/2协议的开发在TLS的握手层面需要进行协议协商,进行协议协商的`协议`被称作ALPN(应用层协议协商)。
Application Layer Protocol Negotiation (ALPN) is a TLS extension that allows client and server to negotiate the application protocol that they will use to communicate within the encryption provided by TLS.
ALPN是TLS的扩展支持,使得客户端与服务端在TLS的加密通讯层可进行协议约定,举个例子:甲同乙沟通,甲用英文向乙说,你会说中文吗?
乙说我会讲,甲说那我们用中文来沟通吧,乙说可以的。如过乙说不会讲,甲乙继续保持英文交流。
Any protocol can be negotiated by ALPN within a TLS connection; the protocols that are most commonly negotiated are HTTP/2 and HTTP/1.1.
在TLS链接层面ALPN支持任意协议协商,通常最广泛进行协商的协议是 HTTP/2、HTTP/1.1。
Browsers only support HTTP/2 over TLS by negotiating the HTTP/2 protocol via ALPN. You need to configure the server to support TLS and ALPN if you want browsers to use the HTTP/2 protocol, otherwise they will default to HTTP/1.1.
浏览器仅支持HTTP/2 over TLS,还有一种HTTP/2 overcleartext,前一种简称h2、后一种简称h2c。over TLS是加密链接,后一种未加密明文。如果你想让你的服务在浏览器层面支持HTTP/2,你需要配置你的服务器端支持TLS 和 ALPN。
二、 基于jetty的升级
a) ALPN 支持
When using Jetty embedded, the jetty-alpn-client
and jetty-alpn-server
artifacts must be included in the classpath, respectively for client and server use cases.
基于嵌入jetty的应用,服务端和客户端需要分别引入jetty-alpn-server 和 jetty-alpn-client jar包。
The ALPN implementation is provided to these two artifacts with the following three options:
ALPN的支持有三种方案可选
-
For JDK 8 only, a provider based on modified OpenJDK classes
- Only works with JDK 8, pure Java implementation
- Requires the
-Xbootclasspath/p
option on command line
仅jdk8, 通过修改OpenJDK的类来支持,需要在启动命令里上 java -Xbootclasspath/p:yourdir/alpn-boot-8.1.13.v20181017.jar 选项。
-
For JDK 8 or later, a provider based on the Conscrypt security provider
- Works with JDK 8 or later and provides improved performance
- Binds to the OpenSSL native library shipped by Conscrypt and is therefore only available on the platforms supported by Conscrypt
对jdk8及以后,借助native library Conscrypt的支持,因此仅对支持Conscrypt的平台可用,提供最佳的性能。
-
For JDK 9 or later, a provider based on the ALPN APIs present in the JDK
- Works with JDK 9 or later, pure Java implementation
- Lower performance than Conscrypt
对jdk9及以后,ALPN的支持在JDK层面实现,不需要引入额外的依赖,性能相比于Conscrypt略低。
b) 服务端改造引入jar包
实验方案采用第一种,仅JDK8。
<dependency> <groupId>org.eclipse.jetty.http2</groupId> <artifactId>http2-server</artifactId> <version>9.4.24.v20191120</version> </dependency> <dependency> <groupId>org.eclipse.jetty.alpn</groupId> <artifactId>alpn-api</artifactId> <version>1.1.3.v20160715</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-alpn-openjdk8-server</artifactId> <version>9.4.24.v20191120</version> </dependency> <dependency> <groupId>org.mortbay.jetty.alpn</groupId> <artifactId>alpn-boot</artifactId> <version>8.1.13.v20181017</version> <scope>test</scope> </dependency>
c) 代码改造
connector的port参数必须明确设置。
// HTTP Configuration HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSecureScheme("https"); httpConfig.setSecurePort(8443); httpConfig.setSendServerVersion(true); // HTTP Connector ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig)); httpConnector.setPort(8080); server.addConnector(httpConnector); // HTTP/2 Connector ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2, new HttpConnectionFactory(httpsConfig)); http2Connector.setPort(8443); server.addConnector(http2Connector);
以上代码,8080端口既可支持http1.1又支持 http/2, 但8443端口仅支持http/2,且访问地址为https前缀。
#h2c http2 over clear text curl -i --http2 "http://localhost:8080/api?1" #http1.1 curl -i "http://localhost:8080/api?1" #h2 http/2 over tls curl -i "https://localhost:8443/api?1"
三、 基于Springboot的升级 (引用)
a) 多种webserver方案
- With undertow 1.4.0+,在jdk8层面不需要任何依赖
- With jetty,need Conscrypt support
- With tomcat, if jdk9 or later 不需要任何依赖,if jdk8, 通过JNI 调用libtcnative 来支持,需要在操作系统环境层面安装libtcnative及其关联依赖
- With Reactor Netty
b) 选择jdk8 tomcat 方案
原因,tomcat为springboot的默认webserver。
- 安装libtcnative,参考
- apr 1.7.0
- openssl 1.1
- GNU development environment (libtool g++ gcc make perl linux-headers)
- tomcat-native 1.2.23
libtcnative 最好是集成到docker镜像里,以免新装机器的环境都要重新安装。
- properties配置文件
要支持http2, 必须配置ssl
# ssl config
server.ssl.key-store=yourdir/your.jks
server.ssl.key-store-password=yourstorepwd
server.ssl.trust-store=yourdir/your.cacerts
server.ssl.trust-store-password=yourtrustpwd
# http2 config
server.port=8443
server.http2.enabled=true
c) 代码改造
@Configuration
public class WebServerConfigure implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// reference `https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-enable-multiple-connectors-in-tomcat`
Connector httpConnector = new Connector();
httpConnector.setScheme("http");
httpConnector.setPort(8080);
factory.addAdditionalTomcatConnectors(httpConnector);
}
}
通过properties配置支持https的端口号为8443, 通过以上代码支持http的端口号为8080。需要说明的是,基于tomcat来适配http2,无法支持h2c,只能要么是h2,要么http1.1。遇到一个诡异的问题是,如果不通过配置文件配置http2, 通过以上代码方式添加http2的connector,应用启动会报调用libtcnative的错。
d) 应用启动
java -Djava.library.path=/usr/local/opt/tomcat-native/lib , 需要指定libtcnative的路径。
四、 结语
虽然http2有很多好处,但是它有加解密的开销、建立安全连接需握手4次,而常规http握手3次。如果业务请求量不大,可能不会带来性能提升,甚至是性能损耗。http2的常规使用场景是外网至网关层采用http2的安全协议,网关至后端服务可以采用http协议或非加密版本的http2,即h2c。
五、 参考链接
https://www.eclipse.org/jetty/documentation/current/http2-configuring-haproxy.html
https://stackoverflow.com/questions/44243764/http-2-issue-with-jetty-invalid-preface
https://www.jianshu.com/p/7ddcdd3847d6
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-embedded-web-servers
https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html