Java9系列第九篇-對HTTP2協議的支援與非阻塞HTTP-API

字母哥部落格 發表於 2020-10-28

Java9系列第九篇-對HTTP2協議的支援與非阻塞HTTP-API

在HTTP/1.1 釋出了16 年之後,IETF在2015年終於通過了HTTP/2 協議。HTTP/2協議旨在降低延遲,滿足當今時代對於資訊響應時間的要求。在這篇文章中,我會簡要的對HTTP/2協議進行介紹,然後我們將重點放在研究Java9中對HTTP/2支援及其HTTP客戶端API的變化。

一、HTTP/2簡介

  • HTTP/2 旨在減輕 HTTP/1.1 維護複雜基礎結構所造成的痛苦,效能良好。儘管 HTTP/2 仍然與 HTTP/1.1 向後相容,但它不再是基於文字的協議。
  • HTTP/2 多路複用使單個連線可以處理多個雙向流,允許客戶端通過單個連線同時下載多個資源。
  • HTTP 1.x 協議是基於文字的,因此報文很冗長。有的時候,同一組 HTTP Headers被一遍又一遍地交換。HTTP/2 通過跨請求維護 HTTP Headers,消除重複交換的資料,大大減少了資料互動所需的頻寬。

HTTP/2資料推送

您可能認為HTTP/2的服務端資料推送是對 WebSockets 的某種延續或升級,但情況並非如此。雖然 WebSockets 是客戶端和伺服器之間全雙工通訊的一種方法,以便伺服器在建立 TCP 連線後將資料傳送到客戶端,但 HTTP/2 提供了一種不同的解決方案。

HTTP/2 推送是主動向客戶端傳送資源,而無需從客戶端的角度發起資源請求。這意味著伺服器端根據一個請求可能知道網站進一步需要的其他資源,並且早在客戶端再次發起請求它們之前,就可以一併(提前)傳送所有資源。

目前支援 HTTP/2 的 Java HTTP 客戶端

  • Jetty
  • Netty
  • OkHttp
  • Vert.x
  • Firefly

但是在這篇文章中,我們不會介紹這些Java 客戶端軟體,而是介紹Java9提供的HTTP/2支援。

二、Java 9 的 HTTP/2 客戶端

首先使用Java 9的語法進行模組的匯入 。jdk.incubator.httpclient

module com.springui.echo.client {
    requires jdk.incubator.httpclient;
}

Java 9 新的 HTTP Cient API 遵循構建器模式。HttpClient是用來操作HTTP請求的入口點,先構建後使用。

HttpClient client = HttpClient
    .newBuilder()
    .version(Version.HTTP_2)  //支援HTTP2
    .build();

在阻塞模式下傳送請求

一旦我們有了一個 HttpClient例項,就可以用它來傳送HttpRequest,HttpRequest例項也可以使用構造器建立。

HttpResponse<String> response = client.send(
    HttpRequest
        .newBuilder(TEST_URI)  //請求地址
        .POST(BodyProcessor.fromString("Hello world")) //POST報文資料
        .build(),
    BodyHandler.asString()  //請求響應資料處理,接收字串
);

請求發出去之後,執行緒會一直阻塞直到得到響應資料,這個和JAVA 8及之前的HTTP API是一樣的。但是Java 9提供了以非同步非阻塞傳送處理請求的方法,更適合高併發的HTTP請求與處理。

以非阻塞模式傳送請求(Java 9)

在下面的示例中,10 個隨機整數以非同步方式傳送請求。

List<CompletableFuture<String>> responseFutures = 
        IntStream.of(1,2,3,4,5,6,7,8,9,10)  //10個整數形成IntStream,Java 8的語法
        .mapToObj(String::valueOf) //10個整數轉換成字串,Java 8的語法
        .map(message -> client.sendAsync( //將10個整數字符串作為內容,傳送10個非同步請求
                HttpRequest.newBuilder(TEST_URI)
                        .POST(HttpRequest.BodyProcessor.fromString(message))
                        .build(),
                HttpResponse.BodyHandler.asString()
             ).thenApply(HttpResponse::body)  //以CompletableFuture<HttpResponse.body()>作為流處理的返回值
        )
        .collect(Collectors.toList());  //將Stream轉成List

上面的例子大量的使用了Java 8的Stream流式處理的API,如果不熟悉的同學,可以翻看我以前寫的一些文章。
sendAsync方法的返回值CompletableFuture<HttpResponse<String>>,使用thenApply(HttpResponse::body)作了進一步的處理,最終返回值是CompletableFuture<String>
CompletableFuture是Java非同步程式設計的知識,將併發的非同步處理結果列印出來。

responseFutures.stream().forEach(future -> {
    LOGGER.info("Async response: " + future.getNow(null));
});

你會注意到,最終的列印日誌可能不是按照順序1、2、3、4、5、6、7、8、9、10進行處理的,因為所有的請求都是非同步傳送出去的,返回的結果是CompletableFuture用於非同步處理的結果。

三、支援HTTP2的Push-Promise Frames

以上所有示例在 HTTP/1.1協議下都可以完成,只是新加了非阻塞的非同步API,也沒涉及到 HTTP/2 特性啊。別急,Java 9 Client API與HTTP/2結合最緊密的就是:可以使用HTTP2傳送一個請求,得到多個非同步資料結果。(某些資料提前推送,當然這需要服務端也支援HTTP/2進行配合)

Map<HttpRequest,CompletableFuture<HttpResponse<String>>> responses =
        client.sendAsync(    //注意這裡只傳送一次請求
          HttpRequest.newBuilder(TEST_URI)
                  .POST(HttpRequest.BodyProcessor.fromString(TEST_MESSAGE))
                  .build(),
          HttpResponse.MultiProcessor.asMap(    //多個資源的響應結果
                  request -> Optional.of(HttpResponse.BodyHandler.asString())
          )
).join();

responses.forEach((request, responseFuture) -> {
  LOGGER.info("Async response: " + responseFuture.getNow(null));
});

從Java 9的角度來看,新的HTTP/2客戶端API看起來不錯。但筆者覺得目前相關技術的使用還不是很成熟,我覺得大家暫時嚐嚐鮮就可以。

歡迎關注我的部落格,裡面有很多精品合集

  • 本文轉載註明出處(必須帶連線,不能只轉文字):字母哥部落格

覺得對您有幫助的話,幫我點贊、分享!您的支援是我不竭的創作動力! 。另外,筆者最近一段時間輸出瞭如下的精品內容,期待您的關注。