在Java生態圈裡,若想發起HTTP請求,用戶端的選擇非常豐富。之所以如此,一方面是因為HTTP標準的發展:1996年HTTP1.0至1999年HTTP1.1,僅間隔三年,然而,卻過了十六年,才於2015年正式批淮HTTP/2。

另一方面,是標準API的HttpURLConnection,用起來手續繁雜,在這十幾年中,Web技術前後端的變化,以及Android的興起,種種因素造就各時期需求不同,因而有了各個HTTP用戶端的興起與更迭。

Java的HTTP用戶端

若是Java領域資深開發者,對於Apache Commons HttpClient一定不陌生。它從2001年開始,對Java標準API做了封裝,2005年由HttpComponents專案取代;Android 1.0內建HttpClient,後來Android為了API的一致性,凍結了HttpClient 4.0,本來期待Google對其做出維護,然而並沒有,到了Android 6.0以後,也不再內建HttpClient。

Android開發者後來熟悉的是Square的OkHttp,Android 4.4也用OkHttp實現HttpURLConnection,隨著後來RESTful的概念興起,Square也基於OkHttp推出Retrofit,實現RESTful風格的操作;當然,談到RESTful,熟悉Spring的開發者,應該會馬上聯想到Spring的RestTemplate。

HTTP請求會有非同步的需求,像是OkHttp這類基於回呼模型的程式庫,在各個請求回應的銜接需求複雜時,容易面臨回呼地獄的問題,後來興起了Reactive運動,也有了RxJava之類的實現,可以整合OkHttp、Retrofit,實現Reactive風格的操作,甚至後來也推動了Reactive的標準化。

此外,Spring的Reactor也實現了Reactive Streams規範,就HTTP用戶端而言,其WebFlux基於Reactor,提供了WebClient。

當然Java的HTTP用戶端發展史,並不是只有以上談到的程式庫,重點在於藉由瞭解這個過程,認識HTTP請求時所伴隨的一些需求;回過頭來,JDK本身的方案呢?有的!Java 11推出了HttpClient。

Java 11 HttpClient API

談到非同步,以及Reactive的風潮、需求與標準化,這也推動了JDK本身,Java 8提供可實現非同步處理的CompletableFuture,Java 9正式納入Reactive規範,在Flow類別上,定義四個介面,各介面定義的方法簽署與org.reactivestreams套件的定義一致。

如果你對這段歷史發展有興趣,可參考先前專欄〈Reactive與Java 9〉,這裡也談到,Flow API尚在等待實作品,但更精確地說,是在等待正式實作品,Java 9其實就有處於孵化階段的Flow API實作品,也就是後來於Java 11正式推出的HttpClient API。

想認識HttpClient API用法,網路有許多文件,或者可直接參考OpenJDK官網的〈Introduction to the Java HTTP Client〉,可以認識基本使用方式。另一個則是〈Examples and Recipes〉,我們可看到在JSON請求回應、Proxy等需求下如何撰寫程式。

如果只是想使用HttpClient等現成的API,來組織想要的網路請求與回應,大概看個幾篇文件就能上手了,例如,一個簡單的POST請求回應:

var request = HttpRequest.newBuilder(uri).POST(ofString("k=v")).build();
var response = client.send(request, ofString());
var body = response.body();

就算沒正式看過文件,應該也不難理解這個程式片段的作用,其中client是HttpClient.newHttpClient方法建立的HttpClient實例,而send()是同步方法,傳回HttpResponse實例,透過body方法可以取得字串回應,對於非同步操作,HttpClient可以透過sendAsync方法,取得CompletableFuture實例,後續透過thenXXX等方法,可以進一步處理本體。

HttpClient API與Reactive

CompletableFuture?就這樣嗎?不是說HttpClient API是Flow API的實作品嗎?Reactive的部份呢?首先要知道的是HttpClient實例,實質上就等效於請求本體的訂閱者、回應本體的發布者。

至於請求本體真正的發布者,是HttpRequest.BodyPublishers的ofXXX方法傳回的BodyPublisher實例,而BodyPublisher是Flow.Publisher的子介面,例如上面程式片段POST方法中看到的ofString,指定了字串作為本體來源,將資料發布給HttpClient,BodyPublishers也有ofFile、ofInputStream等方法,可在檔案、串流作為本體來源時使用。

上例中BodyHandlers的ofXXX會傳回BodyHandler實例,BodyHandler是個函式介面(functional interface),其apply方法會傳回BodySubscriber實例,而BodySubscriber是Flow.Subscriber的子介面,BodySubscriber顧名思義是作為回應本體的訂閱者,上例中send方法中的ofString,最終是向HttpClient訂閱回應,然後轉換為字串,這也正是BodyHandler會在呼叫send(sendAsync)時指定的原因。

也就是說,從請求到回應的過程,BodyPublisher會作為請求本體的資料來源,它會發布資料,而HttpClient實質上作為資料的訂閱者,轉換為HTTP協定內容後進行請求;當HttpClient收到回應,會作為回應本體的資料來源,它會發布資料,而你指定的BodyHandlers.ofXXX方法,最後會取得BodySubscriber實例作為訂閱者,將收到的回應資料轉為想要的格式,然後,透過HttpResponse的body方法取得(同步)或用thenXXX處理(非同步)。

在CompletableFuture不敷使用時,若能瞭解這個API架構,要在各種請求、回應之間進行流式銜接的方式,就清晰了。例如,想將某個來源作為請求本體發送,就是實作BodyPublisher後、指定實例給HttpRequest,BodyPublishers本身也有fromPublisher,可便於銜接Flow.Publisher的實作。

若是想將網站的回應做進一步處理,那麼,就實作BodyHandler。其中的BodySubscriber實作會訂閱回應,至於該怎麼轉換回應?就是在BodySubscriber中實作,BodyPublishers本身也有fromSubscriber,可以便於銜接Flow.Subscriber的實作。

想對Java 11的HttpClient有更深入的認識,可參考〈Java 11 Reactive HTTP Client〉這個議程,其中也有BodyPublisher、BodySubscriber的實作示範。

瞭解歷史與API架構

當我們收集、整理Java生態圈HTTP用戶端方案,單看入門文件或範例程式碼,老實說對於釐清哪個能符合需求無太大幫助,反而要認識各方案發展過程,比較能知道哪個環境或需求下,該使用哪個。像是:在Android使用OkHttp與RxJava?已有Spring或WebFlux,或許就用RestTempalte、WebClient?想要原生標準API?Java 11的HttpClient如何呢?

另一方面,認識API架構絕對是有幫助的,畢竟在各方案現成的API不敷使用時,自行封裝、實作、組合就會是必要之舉,這時API設計上是否有彈性,能否符合需求就會是重要考量,這邊是以Java 11 HttpClient API來作剖析,畢竟相對而言,它對開發者來說比較新鮮,可以順便認識一下其API架構。

面對其他方案,也建議對API架構做類似剖析,多找原始碼分析的文件,最好是自行翻閱各方案原始碼,作為實作參考,如Java 11 HttpClient API的BodyPublishers、BodyHandlers上ofXXX方法的相關實作,就是實作發布者、訂閱者很好的參考對象。

專欄作者

熱門新聞

Advertisement