前言
HTTP協(xié)議由于其無(wú)狀態(tài)的特性以及超高的普及率,是當(dāng)下大部分網(wǎng)站選擇使用的應(yīng)用層協(xié)議。然而,HTTP/1.x的底層傳輸方式的幾個(gè)特性,已經(jīng)對(duì)應(yīng)用的整體性能產(chǎn)生了負(fù)面影響。特別是,HTTP/1.0在每次的TCP連接上只允許發(fā)送一次請(qǐng)求,在HTTP/1.1中增加了請(qǐng)求管線,但是這僅僅解決了部分的并發(fā)問(wèn)題,并且仍然存在阻塞的現(xiàn)象,因此需要發(fā)送多個(gè)請(qǐng)求的HTTP/1.0和HTTP/1.1客戶端就與服務(wù)器建立多個(gè)連接,以達(dá)到高并發(fā)低延遲的目的。
另外,HTTP請(qǐng)求頭中的字段經(jīng)常重復(fù)且冗長(zhǎng),帶來(lái)不必要的網(wǎng)絡(luò)流量,并會(huì)導(dǎo)致初始的TCP擁塞窗口被快速填滿。當(dāng)在一個(gè)新的TCP連接上發(fā)送多個(gè)請(qǐng)求時(shí),這可能導(dǎo)致更大的延遲。
HTTP/2通過(guò)在底層連接上定義更優(yōu)的HTTP語(yǔ)義來(lái)解決這些問(wèn)題。具體來(lái)說(shuō),它引入了流、幀的二進(jìn)制傳輸方式,每個(gè)請(qǐng)求由唯一的流id標(biāo)識(shí),允許在同一個(gè)連接上交錯(cuò)地傳輸請(qǐng)求和響應(yīng)信息,并對(duì)HTTP請(qǐng)求頭字段使用高效的壓縮編碼。它還允許對(duì)請(qǐng)求設(shè)置優(yōu)先特權(quán),讓更重要的請(qǐng)求更快地完成,從而進(jìn)一步提高性能。
已經(jīng)使用HTTP/2協(xié)議的網(wǎng)站也有很多,像Google、Twitter、Akamai等這些國(guó)外的公司早就支持HTTP/2了,國(guó)內(nèi)的網(wǎng)站比如百度、知乎、新浪、阿里、京東等也都已經(jīng)全面支持HTTP/2,所以HTTP/2已經(jīng)過(guò)了前期推廣的階段,開(kāi)始逐漸普及了。
HTTP/2特性
HTTP協(xié)議從1991年的HTTP 0.9 到 HTTP 1.0、HTTP 1.1 再到 2015年開(kāi)始至今的HTTP 2.0,每個(gè)版本都帶來(lái)了驚人的升級(jí)體驗(yàn)。HTTP/2的驚人之處可以在這里體驗(yàn)一下,可以感受到相比HTTP 1.1在性能上的大幅提升。
特性
- 新的二進(jìn)制格式:HTTP 1.x的解析是基于文本,HTTP 2.0的解析是基于二進(jìn)制,更加適用于服務(wù)器間信息傳輸了。
- 多路復(fù)用(MultiPlexing):也就是連接共享,每個(gè)請(qǐng)求都有一個(gè)ID,這樣在一個(gè)連接上可以發(fā)送多個(gè)請(qǐng)求,并且它們?cè)趥鬏斶^(guò)程中是混雜在一起的,接收方可以根據(jù)請(qǐng)求的ID將請(qǐng)求再歸屬到不同的服務(wù)端請(qǐng)求里面。
- 請(qǐng)求優(yōu)先級(jí)(request prioritization):HTTP/2中,一個(gè)源只有一個(gè)連接來(lái)實(shí)現(xiàn)多路復(fù)用,所有資源通過(guò)一個(gè)連接傳輸,為了避免線頭堵塞(Head Of Line Block),這時(shí)資源傳輸?shù)捻樞蚓透匾?。?yōu)先加載重要資源,可以盡快渲染頁(yè)面,提升用戶體驗(yàn)。
- header壓縮:通信雙方各自緩存一份Header fields表,只傳輸壓縮之后的相應(yīng)編碼,避免了重復(fù)的header傳輸。
- 服務(wù)端推送(server push):這個(gè)就很好理解了,HTTP 1.x都是只能從client拉取資源,HTTP/2支持從server端推送資源至client端。
HTPP 2.0的多路復(fù)用和HTTP 1.x的長(zhǎng)連接的區(qū)別
- HTTP 1.0中一次請(qǐng)求響應(yīng)就建立一次連接,用完關(guān)閉,每個(gè)請(qǐng)求都要建立一個(gè)連接。
- HTTP 1.1 Pipeling解決方式,也是我們常說(shuō)的Keep-Alive模式,建立一次連接之后,若干個(gè)請(qǐng)求排隊(duì)串行化單線程處理,后面的請(qǐng)求等待前面的請(qǐng)求返回了獲得執(zhí)行機(jī)會(huì),一旦有請(qǐng)求超時(shí)等待,后續(xù)的請(qǐng)求只能被阻塞,毫無(wú)辦法,也就是人們常說(shuō)的線頭阻塞(Head-of-Line Blocking)。
Spring Boot 實(shí)現(xiàn) HTTP/2 服務(wù)器
本篇主要討論HTTP/2協(xié)議實(shí)現(xiàn),springboot-web具體接口開(kāi)發(fā)已提前準(zhǔn)備好。
在Spring Boot應(yīng)用中常用到的WEB服務(wù)器有:Undertow、Jetty、Tomcat三個(gè),使用這三個(gè)WEB服務(wù)器都能配置出支持HTTP/2的Spring Boot服務(wù),具體步驟參見(jiàn)官方文檔。
HTTP/2 與 Jetty
從 Jetty 9.4.8 開(kāi)始, Conscrypt 庫(kù)也支持 HTTP/2 。要啟用該支持,您的應(yīng)用程序需要有兩個(gè)額外的依賴項(xiàng): org.eclipse.jetty:jetty-alpn-conscrypt-server和org.eclipse.jetty.http2:http2-server.
HTTP/2 與 Tomcat
Tomcat 8.5.x + JDK8版本下支持HTPP/2需要安裝相應(yīng)的本機(jī)庫(kù)。您可以使用 JVM 參數(shù)來(lái)執(zhí)行此操作,例如 -Djava.library.path=/usr/local/opt/tomcat-native/lib. 更多信息請(qǐng)參閱 Tomcat 官方文檔。
或者使用Tomcat 9.0.x + JDK9版本,也可支持HTTP/2。
HTTP/2 與 Undertow
從Undertow 1.4.0+開(kāi)始,就可以支持JDK8版本下的HTTP/2,無(wú)需其他任何要求。
本篇選用是Undertow,由于SpringBoot內(nèi)嵌的是tomcat服務(wù)器,我們需要在Spring Boot的依賴中排除默認(rèn)的tomcat服務(wù)器,引入undertow服務(wù)器,方法是修改項(xiàng)目的POM文件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 從依賴信息里移除 Tomcat配置 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入undertow服務(wù)器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
<exclusions>
<exclusion>
<groupId>org.jboss.xnio</groupId>
<artifactId>xnio-nio</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.xnio</groupId>
<artifactId>xnio-nio</artifactId>
<version>3.8.7.Final</version>
</dependency>
關(guān)于HTTP2中HTTPS說(shuō)明:
HTTP2.0其實(shí)可以支持非HTTPS的,但是現(xiàn)在主流的瀏覽器像chrome,firefox表示還是只支持基于 TLS 部署的HTTP2.0協(xié)議,所以要想升級(jí)成HTTP2.0還是先升級(jí)HTTPS為好。(我也嘗試了只添加HTTP/2配置,不配置HTTPS,使用chrome瀏覽器訪問(wèn)確實(shí)仍為http/1.1,相關(guān)配置并未生效)
配置HTTPS:
使用jdk自帶keytool生成安全證書(shū)(自簽名):
keytool -genkeypair -alias certificatekey -keyalg RSA -validity 365 -keystore shfqkeystore.jks
這條命令會(huì)在生成keystore后接著生成一個(gè)密鑰對(duì)。
certificatekey是別名,可以自己指定。
RSA是非對(duì)稱密鑰算法,也可以改為 keytool支持的其他密鑰算法。
365代表的是證書(shū)的有效期,單位天,可以自己指定。
shfqkeystore.jks是keystroe的名稱,可以自己指定。
執(zhí)行該命令后會(huì)提示輸入密碼口令、身份信息,按照提示輸入即可,最后選擇信任此證書(shū)。
完成上述步驟,即會(huì)在當(dāng)前目錄生成證書(shū),將證書(shū)放在resources目錄下。
添加application.yml配置:
server:
port: 9002 #https默認(rèn)接口為8443,此處可以自己修改
ssl:
key-store: classpath:shfqkeystore.jks
key-store-password: 123456
key-password: 123456
enabled: true
此時(shí)使用Chrome訪問(wèn)項(xiàng)目中鏈接:https://localhost:9002/http2/visit
可以看到可以通過(guò)https協(xié)議訪問(wèn),但協(xié)議仍為http/1.1
實(shí)現(xiàn)HTTPS之后,配置HTTP/2就相當(dāng)簡(jiǎn)單了,只需要在application.yml配置中添加
server.http2.enable=true
server:
port: 9002
ssl:
key-store: classpath:rabbitkeystore.jks
key-store-password: 123456
key-password: 123456
enabled: true
http2:
enabled: true
此時(shí)使用Chrome仍然訪問(wèn)鏈接:https://localhost:9002/http2/visit
可以看到已經(jīng)為http/2協(xié)議
補(bǔ)充:
1.若網(wǎng)頁(yè)提示您的連接不是私密連接問(wèn)題,輸入 thisisunsafe 即可(不用管顯示在哪里,直接在鍵盤(pán)上輸入即可);
2.若您在Chrome的開(kāi)發(fā)者工具中沒(méi)有顯示請(qǐng)求的協(xié)議,右鍵將Protocol項(xiàng)選中即可;
使用java代碼實(shí)現(xiàn)HTTP/2調(diào)用
了解到JDK9之后okhttp3可以支持HTTP/2,于是我們使用springboot配合okhttp3來(lái)實(shí)現(xiàn)java代碼的HTTP/2調(diào)用,并將java版本選擇為穩(wěn)定版的JDK11。(JDK自帶的Http Client,也要JDK9版本以后才支持HTTP/2,大家也可嘗試)
引入pom依賴:
srpingboot:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
</parent>
okhttps:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
將服務(wù)器中生成的安全證書(shū)copy到該項(xiàng)目的resources目錄下。
代碼實(shí)現(xiàn):
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import javax.net.ssl.*;
import java.io.IOException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class Http2ClientUtil {
// 服務(wù)器ip
public static String host = "https://127.0.0.1";
// 服務(wù)器端口
public static int port = 9002;
private static OkHttpClient client;
static {
try {
initClient();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// GET請(qǐng)求接口不帶參數(shù)
public static String doGetNoParams(String path) {
String result;
try {
String url = host + ":" + port + path;
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
result = response.body().string();
String protocol = response.protocol().name();
System.out.println("server result:" + result);
System.out.println("client protocol:" + protocol);
} catch (IOException e) {
result = "failed";
e.printStackTrace();
}
return result;
}
/**
* 初始化生成OkHttpClient client
*/
public static void initClient() throws Exception {
// 導(dǎo)入公鑰證書(shū)
KeyStore keyStore = KeyStore.getInstance("JKS"); // .jks格式
Resource resource = new ClassPathResource("shfqkeystore.jks");//路徑基于resources
keyStore.load(resource.getInputStream(), "123456".toCharArray());//密鑰口令
// 初始化證書(shū)管理factory
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init(keyStore);
// 獲得X509TrustManager
TrustManager[] trustManagers = factory.getTrustManagers();
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
// 初始化sslContext
SSLContext sslContext = SSLContext.getInstance("TLS"); // 這里SSL\TLS都可以
sslContext.init(null, new TrustManager[] { trustManager }, null);
// 獲得sslSocketFactory
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
// 初始化client builder
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//關(guān)鍵步驟,在這里將HTTP/2協(xié)議添加進(jìn)去
List<Protocol> protocols = new ArrayList<>();
protocols.add(Protocol.HTTP_1_1); // 這里如果,只指定h2的話會(huì)拋異常
protocols.add(Protocol.HTTP_2); // 這里如果,只指定h2的話會(huì)拋異常
builder.sslSocketFactory(sslSocketFactory, trustManager)
.protocols(protocols) // 設(shè)置builder protocols
.hostnameVerifier(new HostnameVerifier() { // 放過(guò)host驗(yàn)證
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
// build client
client = builder.build();
}
}
我們使用main方法測(cè)試:
public static void main(String[] args) throws InterruptedException {
Http2ClientUtil.doGetNoParams("/http2/visit");
}
控制臺(tái)打?。?br>文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-408468.html
至此,成功基于springboot & HTTP/2實(shí)現(xiàn)服務(wù)端、客戶端。
感謝閱讀!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-408468.html
到了這里,關(guān)于基于Spring Boot2.0 & HTTP/2 實(shí)現(xiàn)服務(wù)器、客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!