一、前言
HTTP/2 作為 HTTP/1.1 的繼任者,在效能、效率以及用戶體驗方面帶來了顯著的提升。它引入了多路複用、頭部壓縮、伺服器推送等關鍵特性,大幅減少了網頁載入時間並優化了資源利用率。HTTP/2 並不僅限於傳輸文本數據,它也能高效地傳輸二進制資料,例如圖像、音頻和影片等。然而,Java 在對 HTTP/2 的支援上並非一蹴而就,而是在不同版本中逐步完善。
值得注意的是,Java 原生 API (例如
java.net.http
包) 主要提供了 HTTP/2 客戶端 (Client) 的支援,並沒有提供直接構建 HTTP/2 伺服器 (Server) 的方式。
因此,在 Java 中建立 HTTP/2 伺服器通常需要依賴第三方函式庫,例如 Netty。本文將深入探討 Java 7 至 17 各版本在 HTTP/2 方面的發展,分析其關鍵特性、架構、相關工具以及具體範例,幫助讀者理解 Java 如何逐步擁抱並應用 HTTP/2,以及如何透過第三方函式庫建立 HTTP/2 伺服器。
目錄
- 一、前言
- 二、Java 7/8:HTTP/2 的萌芽
- 三、Java 9:HTTP/2 客戶端 API 的曙光
- 四、Java 11:HTTP Client API 的正式加入
- 五、HTTP Server 和 HTTP/2 Client 範例
- 六、使用第三方套件建立 HTTP/2 伺服器
- 七、Java 12 ~ 17:持續的改進與優化
- 八、總結
- 附錄
二、Java 7/8:HTTP/2 的萌芽
在 Java 7 和 8 中,官方標準庫並未直接提供 HTTP/2 的原生支援。當時,如果開發者需要使用 HTTP/2,通常需要依賴第三方函式庫,例如:
- Netty: 一個高度可擴展的非同步事件驅動網路應用框架,提供了強大的 HTTP/2 支援。
- Jetty: 一個輕量級的 Java HTTP 伺服器和 Servlet 容器,通過其 HTTP/2 模組可以支援 HTTP/2。
這時期使用 HTTP/2 往往需要額外的依賴和配置,使得應用程式的部署和維護成本較高。儘管如此,這也為後續 Java 版本原生支援 HTTP/2 打下了基礎。
架構圖(基於第三方函式庫):
graph LR A[客戶端] --> B(第三方函式庫: Netty/Jetty) B --> C[HTTP/2 伺服器]
範例 (使用 Netty - 簡化):
(範例僅供示意,實際程式碼較為複雜,需參考 Netty 文件)
三、Java 9:HTTP/2 客戶端 API 的曙光
Java 9 引入了新的 HTTP Client API ,這是一個重要的里程碑。這個 API 提供了一個現代化且更為易用的 HTTP 客戶端,同時,它也提供了 HTTP/2 的初步支援。雖然 Java 9 的 HTTP Client API 仍處於孵化階段 (Incubator Module),但它展現了 Java 對 HTTP/2 的積極擁抱。
架構圖(Java 9 HTTP Client API):
graph LR A[客戶端] --> B(Java 9 HTTP Client API) B --> C[HTTP/2 伺服器]
工具:
-
java.net.http
相關的類別,例如HttpClient
,HttpRequest
,HttpResponse
。
範例 (Java 9 HTTP Client API - 簡化):
注意: Java 9的HTTP Client API在最初是incubator module, 後續版本才正式成為標准API
四、Java 11:HTTP Client API 的正式加入
Java 11 正式移除了 Incubator Module,將 Java 9 引入的 HTTP Client API 納入標準 API。這意味著開發者可以無需擔心 API 的變動,直接使用
java.net.http
包下的類別進行 HTTP/2 通訊。這大大簡化了開發流程,降低了程式碼的複雜度。Java 11 的 HTTP Client API 支援同步與非同步請求,提供了完善的錯誤處理機制,並可配合 TLS (Transport Layer Security) 使用,安全高效。
架構圖(Java 11 HTTP Client API):
graph LR A[客戶端] --> B(Java 11 HTTP Client API) B --> C[HTTP/2 伺服器]
工具:
-
java.net.http
包中的HttpClient
,HttpRequest
,HttpResponse
,WebSocket
等類別。
範例 (Java 11 HTTP Client API - 同步請求): (與 Java 9 範例類似,但使用正式 API)
範例 (Java 11 HTTP Client API - 非同步請求):
五、HTTP Server 和 HTTP/2 Client 範例
本節使用 Java 內建的
com.sun.net.httpserver.HttpsServer
來建立一個簡單的 HTTPS 伺服器,並使用
java.net.http.HttpClient
建立 HTTP/2 客戶端。 這裡需要 JDK 11 以上的版本才能執行。
資料夾目錄:
說明:
- HTTPS (Hypertext Transfer Protocol Secure): HTTPS 是 HTTP 的安全版本,它通過 TLS/SSL 協定加密客戶端和伺服器之間的通訊,保障資料傳輸的安全性。為了建立 HTTPS 連線,伺服器需要一個憑證。
- 自簽憑證: 為了簡化開發流程,範例使用了自簽憑證。自簽憑證是由伺服器自己生成的憑證,而不是由受信任的憑證頒發機構 (CA) 簽發。在生產環境中,應該使用 CA 簽發的憑證。
-
com.sun.net.httpserver
: 這是 Java SE 提供的一個輕量級 HTTP 伺服器 API。它可以用於快速建立簡單的 HTTP 伺服器,但功能相對有限,不適用於大型或高負載的應用程式。 -
java.net.http.HttpClient
: 這是 Java 9 中引入的標準 HTTP 客戶端 API。它支援 HTTP/1.1 和 HTTP/2,提供了同步和非同步請求等功能。 - HTTP/2: HTTP/2 是 HTTP/1.1 的改進版本,具有多路複用、頭部壓縮等特性,可以提高網頁載入速度和效能。
程式碼結構: 這個範例程式碼被拆分為幾個檔案
(5.1) 使用 Bouncy Castle 建立自簽憑證
SelfSignedCertificateGenerator.java
:
- 功能: 使用 Bouncy Castle 函式庫生成自簽憑證和相關的金鑰儲存 (KeyStore) 檔案。
-
說明:
-
generateRSAKeyPair(int keySize)
: 生成 RSA 金鑰對。 -
createCertificate(KeyPair keyPair, String dnString, int validityDays)
: 使用金鑰對,產生自簽憑證。 -
saveCert(String aliasName, X509Certificate cert, PrivateKey key, char[] keyStorePassword)
: 將憑證與私鑰儲存到金鑰儲存檔案中。
-
- Bouncy Castle: 這是一個第三方加密庫,用於生成自簽憑證證書。
下載 Bouncy Castle 第三方套件: Bouncy Castle 官方網站
- https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk18on/1.79/bcprov-jdk18on-1.79.jar
- https://repo1.maven.org/maven2/org/bouncycastle/bcutil-jdk18on/1.79/bcutil-jdk18on-1.79.jar
- https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk18on/1.79/bcpkix-jdk18on-1.79.jar
(5.2) 使用原生 JAVA SE API 建立 Local Server
SimpleHttpServer.java
:
- 功能: 建立一個簡單的 HTTPS 伺服器,監聽 8443 端口。
-
說明:
-
首先,使用
SelfSignedCertificateGenerator
生成自簽憑證與金鑰,並載入到KeyStore
中。 -
接著,使用
SSLContext
設定 TLS/SSL 連線所需的憑證和密碼。 -
使用
HttpsServer.create()
建立一個HttpsServer
物件,並設定HttpsConfigurator
來配置 SSL。 -
使用
createContext()
設定請求處理邏輯,這裡只簡單返回Hello, HTTP from Simple Server.
的文字訊息。 - 伺服器處理請求時,會輸出 HTTP 版本和請求 Header。
-
首先,使用
(5.3) 使用原生 JAVA SE API 建立創建一個 Http2 Client
SimpleHttp2Client.java
:
-
功能:
建立一個 HTTP/2 客戶端,發送請求到
https://localhost:8443
。 -
說明:
-
建立
HttpClient
物件時,使用version(HttpClient.Version.HTTP_2)
強制使用 HTTP/2。 -
為了避免自簽憑證造成連線錯誤,使用了
TrustManager
來信任所有憑證 (這僅限測試用途, 在生產環境中絕對不要這樣做 )。 - 發送請求後,輸出伺服器的回應 body 和 headers。
-
建立
(5.4) 讓我們執行以上範例
編譯 SelfSignedCertificateGenerator.java :
啟動 Local Server [1]
執行 Client [1:1]
使用 curl 執行請求
Server 端顯示的資訊
(5.5) Http Server 不支援 HTTP/2
即使你嘗試使用 HttpClient
.version(HttpClient.Version.HTTP_2)
或 curl
-http2
參數來發送請求,伺服器端仍然顯示 HTTP/1.1 的原因,很可能是客戶端與伺服器之間 TLS 協商的結果,導致雙方最後選擇使用 HTTP/1.1。
-
問題分析:ALPN (Application-Layer Protocol Negotiation) 缺失
- HTTP/2 的 TLS 連線需要使用 ALPN 擴展,讓客戶端和伺服器在 TLS 握手階段協商應用層協議。如果你的 Java HTTP Server 沒有正確配置 ALPN,它將無法宣告支援 HTTP/2。
- 由於你使用的是 Java 內建的 com.sun.net.httpserver.HttpsServer,它預設並不支援 ALPN。
-
即使客戶端程式碼 (例如
curl -http2
或 Java 內建的 HTTP Client) 要求使用 HTTP/2,但如果伺服器端不支援 ALPN,協商的結果仍會是 HTTP/1.1。
六、使用第三方套件建立 HTTP/2 伺服器
本節將使用 Netty 這個高效能的非同步網路應用程式框架來建立一個支援 HTTP/2 的伺服器。 Netty 提供了對 HTTP/2 的良好支援,並具有非同步、事件驅動、高性能等優點。
下載 Netty 第三方套件: Netty
- https://repo1.maven.org/maven2/io/netty/netty-codec-http2/4.1.116.Final/netty-codec-http2-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.116.Final/netty-codec-http-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-codec/4.1.116.Final/netty-codec-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-handler/4.1.116.Final/netty-handler-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-common/4.1.116.Final/netty-common-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.116.Final/netty-buffer-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-transport/4.1.116.Final/netty-transport-4.1.116.Final.jar
如果您使用 bash ,以下兩個 jar 檔案需要下載:
- https://repo1.maven.org/maven2/io/netty/netty-transport-native-epoll/4.1.116.Final/netty-transport-native-epoll-4.1.116.Final.jar
- https://repo1.maven.org/maven2/io/netty/netty-transport-native-unix-common/4.1.116.Final/netty-transport-native-unix-common-4.1.116.Final.jar
資料夾目錄:
Netty 的核心概念
在深入程式碼之前,我們先來了解一下 Netty 的幾個核心概念,這有助於我們理解程式碼的結構:
-
EventLoopGroup:
負責處理網路事件的執行緒池,Netty 使用非同步 I/O,它包含一或多個
EventLoop
,每個EventLoop
負責處理一個或多個Channel
上的 I/O 事件。-
在我們的範例中,
bossGroup
負責接收新的連線,workerGroup
負責處理連線上的 I/O。
-
在我們的範例中,
-
Channel:
表示一個網路連線的抽象,提供讀取、寫入、關閉等操作。
-
NioServerSocketChannel
是 Netty 用於接收新的 TCP 連線的Channel
實現。
-
-
ChannelPipeline:
Netty 的處理器鍊,資料會在
ChannelPipeline
中的處理器 (ChannelHandler
) 之間傳遞。-
每個
ChannelHandler
可以修改、過濾、攔截、處理資料。
-
每個
-
ChannelHandler:
Netty 中處理 I/O 事件的元件,例如
SslHandler
處理 SSL/TLS 加密,LoggingHandler
輸出日誌。-
我們自訂的
SimpleHttp2ServerHandler
是一個ChannelHandler
,用於處理 HTTP/2 請求。
-
我們自訂的
- Http2FrameCodec: Netty 提供的編碼器和解碼器,將底層的二進位資料轉換為 HTTP/2 框架,方便上層的 Handler 處理。
-
Http2MultiplexHandler:
Netty 提供的處理器,用於處理多個 HTTP/2 串流,它將同一個連線上的多個串流分發到不同的
Channel
上處理。 - ALPN (Application-Layer Protocol Negotiation): TLS 的一個擴展,允許客戶端和伺服器在 TLS 握手階段協商使用哪種應用層協定 (例如 HTTP/1.1 或 HTTP/2)。
範例: HTTP/2 Server
SimpleHttp2Server.java
:
- 功能: 使用 Netty 框架建立一個支援 HTTP/2 的 HTTPS 伺服器。
-
說明:
- 使用 SelfSignedCertificate 生成自簽憑證,用於 HTTPS 連線。
- 透過 SslContextBuilder 配置 SSL 上下文,並啟用 ALPN 來協商 HTTP/2 協議。
- 使用 ServerBootstrap 設定 Netty 伺服器,包括設定 EventLoopGroup 和 Channel 類型。
- 透過 ChannelInitializer 初始化 Channel Pipeline,加入 SslHandler、LoggingHandler、Http2FrameCodec 和 Http2MultiplexHandler 等處理器。
- Http2FrameCodec 負責編碼和解碼 HTTP/2 框架,Http2MultiplexHandler 負責處理多個 HTTP/2 串流。
- 使用自訂的 SimpleHttp2ServerHandler 處理 HTTP/2 請求,並回覆簡單的 "Hello World" 訊息。
- Netty : 一個高性能、非同步事件驅動的網路應用程式框架,用於快速開發高效能的網路伺服器和客戶端。
- ALPN (Application-Layer Protocol Negotiation) : TLS 的一個擴展,允許客戶端和伺服器在 TLS 握手階段協商使用哪種應用層協定 (例如 HTTP/1.1 或 HTTP/2)。
啟動伺服器:
執行 Client
使用 curl 執行請求
Server 端顯示的資訊
Netty官方有提供不少的範例可供學習,例如:
http2-helloworld-server
,在pipeline中使用了升級
UpgradeCodecFactory
的方式處理 HTTP/1.1 與 HTTP/2,有興趣可以下載源碼執行看看。
七、Java 12 ~ 17:持續的改進與優化
Java 12 至 17 主要在 HTTP Client API 的基礎上進行了改進和優化, 這些改進主要集中在性能、穩定性和易用性方面,而非核心架構的變動。
- 性能優化: 在非同步請求處理方面進行了性能提升,使得處理大量並行請求時,程式的效率更高,資源利用率也會更好。
- bug 修復: 針對先前版本發現的 bug 進行了修復,提高了 API 的穩定性和可靠性。
-
新的特性:
- 增加了對 WebSocket 的支援 (Java 11 起) 以及後續版本的完善。
- 其他細節性的改進,例如 API 的易用性、錯誤處理機制、以及對特定邊緣情況的處理等方面的提升,使得 HTTP Client API 更為穩定和易用。
在 Java 12 到 17 期間,HTTP Client API 的核心功能和架構沒有發生重大改變,主要是針對細節方面進行了完善,因此開發者可以繼續使用
java.net.http
包下的類別來進行 HTTP/2 通訊,並享受性能提升和 bug 修復帶來的好處,而無需擔心 API 的不兼容性問題。
架構圖 (Java 12-17 HTTP Client API):
graph LR A[客戶端] --> B(Java 12-17 HTTP Client API) B --> C[HTTP/2 伺服器]
八、總結
從 Java 7 的第三方函式庫到 Java 17 的原生支援,Java 在 HTTP/2 的發展過程中走過了漫長的道路。Java 9 引入的 HTTP Client API 作為關鍵的轉折點,而 Java 11 將其納入標準 API,使得開發者可以更便捷、高效地使用 HTTP/2。
Java 12 至 17 在此基礎上進行了持續的優化和改進,主要集中在性能、穩定性和易用性方面,而非核心架構的變動。
這使得 API 的整體表現更為出色,開發者可以更放心地使用
java.net.http
包進行 HTTP/2 通訊。
總結來說,Java 對 HTTP/2 的支援是一個逐步發展的過程,從最初依賴第三方函式庫,到後來提供原生的客戶端 API,再到後續的持續改進,每一步都為開發者提供了更為便捷高效的工具。 值得注意的是,Java 原生 API 僅提供 HTTP/2 Client 的支援,若要建立 HTTP/2 Server 仍需要依賴第三方套件 (如 Netty)。 開發者應當充分利用這些新特性,以及如 Netty 這類的第三方函式庫,構建更加現代化和高效的網路應用程式。
沒有留言:
張貼留言