在此章中,主要介紹以下內(nèi)容:

  • 什么是HTTP/2 Client API

  • 如何創(chuàng)建HTTP客戶端

  • 如何使HTTP請求

  • 如何接收HTTP響應(yīng)

  • 如何創(chuàng)建WebSocket的endpoints

  • 如何將未經(jīng)請求的數(shù)據(jù)從服務(wù)器推送到客戶端

JDK 9將HTTP/2 Client API作為名為jdk.incubator.httpclient的孵化器模塊。 該模塊導(dǎo)出包含所有公共API的jdk.incubator.http包。 孵化器模塊不是Java SE的一部分。 在Java SE 10中,它將被標準化,并成為Java SE 10的一部分,否則將被刪除。 請參閱 http://openjdk.java.net/jeps/11上的網(wǎng)頁,以了解有關(guān)JDK中孵化器模塊的更多信息。

孵化器模塊在編譯時或運行時未被默認解析,因此需要使用--add-modules選項將jdk.incubator.httpclient模塊添加到默認的根模塊中,如下所示:

<javac|java|jmod...> -add-modules jdk.incubator.httpclient ...

如果另一個模塊讀取并解析了第二個模塊,則也相應(yīng)解析了孵化器模塊。 在本章中,將創(chuàng)建一個讀取jdk.incubator.httpclient模塊的模塊,不必使用-add-modules選項來解析。

因為孵化器模塊提供的API還不是最終的,當在編譯時或運行時使用孵化器模塊時,會在標準錯誤上打印警告。 警告信息如下所示:

WARNING: Using incubator modules: jdk.incubator.httpclient

孵化器模塊的名稱和包含孵化器API的軟件包以jdk.incubator開始。 一旦它們被標準化并包含在Java SE中,它們的名稱將被更改為使用標準的Java命名約定。 例如,模塊名稱jdk.incubator.httpclient可能會在Java SE 10中成為java.httpclient。

因為jdk.incubator.httpclient模塊不在Java SE中,所以將不會為此模塊找到Javadoc。 為了生成此模塊的Javadoc,并將其包含在本書的源代碼中。 可以使用下載的源代碼中的Java9Revealed/jdk.incubator.httpclient/dist/javadoc/index.html文件訪問Javadoc。 使用JDK 9早期訪問構(gòu)建158的JDK版本來生成Javadoc。 API可能會改變,可能需要重新生成Javadoc。 以下是具體的步驟:

  1. 源代碼包含與項目名稱相同目錄中的jdk.incubator.httpclient NetBeans項目。

  2. 安裝JDK 9時,其源代碼將作為src.zip文件復(fù)制到安裝目錄中。 將所有內(nèi)容從src.zip文件中的jdk.incubator.httpclient目錄復(fù)制到下載的源代碼中的Java9revealed\jdk.incubator.httpclient\src目錄中。

  3. 在NetBeans中打開jdk.incubator.httpclient項目。

  4. 右鍵單擊NetBeans中的項目,然后選擇“生成Javadoc”選項。 你會收到錯誤和警告,可以忽略。 它將在Java9Revealed/jdk.incubator.httpclient/dist/javadoc目錄中生成Javadoc。 打開此目錄中的index.html文件,查看jdk.incubator.httpclient模塊的Javadoc。

一. 什么是HTTP/2 Client API?

自JDK 1.0以來,Java已經(jīng)支持HTTP/1.1。 HTTP API由java.net包中的幾種類型組成。 現(xiàn)有的API有以下問題:

  • 它被設(shè)計為支持多個協(xié)議,如http,ftp,gopher等,其中許多協(xié)議不再被使用。

  • 太抽象了,很難使用。

  • 它包含許多未公開的行為。

  • 它只支持一種模式,阻塞模式,這要求每個請求/響應(yīng)有一個單獨的線程。

2015年5月,IETF(Internet Engineering Task Force)發(fā)布了HTTP/2規(guī)范。 有關(guān)HTTP/2規(guī)范的完整文本,請訪問https://tools.ietf.org/html/rfc7540。 HTTP/2不會修改應(yīng)用程序級語義。 也就是說,對應(yīng)用程序中的HTTP協(xié)議的了解和使用情況并沒有改變。 它具有更有效的方式準備數(shù)據(jù)包,然后發(fā)送到客戶端和服務(wù)器之間的電線。 所有之前知道的HTTP,如HTTP頭,方法,狀態(tài)碼,URL等都保持不變。 HTTP/2嘗試解決與HTTP/1連接所面臨的許多性能相關(guān)的問題:

  • HTTP/2支持二進制數(shù)據(jù)交換,來代替HTTP/1.1支持的文本數(shù)據(jù)。

  • HTTP/2支持多路復(fù)用和并發(fā),這意味著多個數(shù)據(jù)交換可以同時發(fā)生在TCP連接的兩個方向上,而對請求的響應(yīng)可以按順序接收。 這消除了在對等體之間具有多個連接的開銷,這在使用HTTP/1.1時通常是這種情況。 在HTTP/1.1中,必須按照發(fā)送請求的順序接收響應(yīng),這稱為head-of-line阻塞。 HTTP/2通過在同一TCP連接上進行復(fù)用來解決線路阻塞問題。

  • 客戶端可以建議請求的優(yōu)先級,服務(wù)器可以在對響應(yīng)進行優(yōu)先級排序時予以遵守。

  • HTTP首部(header)被壓縮,這大大降低了首部大小,從而降低了延遲。

  • 它允許從服務(wù)器到客戶端的資源推送。

JDK 9不是更新現(xiàn)有的HTTP/1.1 API,而是提供了一個支持HTTP/1.1和HTTP/2的HTTP/2 Client API。 該API旨在最終取代舊的API。 新API還包含使用WebSocket協(xié)議開發(fā)客戶端應(yīng)用程序的類和接口。 有關(guān)完整的WebSocket協(xié)議規(guī)范,請訪問https://tools.ietf.org/html/rfc6455。 新的HTTP/2客戶端API與現(xiàn)有的API相比有以下幾個好處:

  • 在大多數(shù)常見情況下,學習和使用簡單易用。

  • 它提供基于事件的通知。 例如,當收到首部信息,收到正文并發(fā)生錯誤時,會生成通知。

  • 它支持服務(wù)器推送,這允許服務(wù)器將資源推送到客戶端,而客戶端不需要明確的請求。 它使得與服務(wù)器的WebSocket通信設(shè)置變得簡單。

  • 它支持HTTP/2和HTTPS/TLS協(xié)議。

  • 它同時工作在同步(阻塞模式)和異步(非阻塞模式)模式。

新的API由不到20種類型組成,其中有四種是主要類型。 當使用這四種類型時,會使用其他類型。 新API還使用舊API中的幾種類型。 新的API位于jdk.incubator.httpclient模塊中的jdk.incubator.http包中。 主要類型有三個抽象類和一個接口:

HttpClient classHttpRequest classHttpResponse classWebSocket interface

HttpClient類的實例是用于保存可用于多個HTTP請求的配置的容器,而不是為每個HTTP請求單獨設(shè)置它們。 HttpRequest類的實例表示可以發(fā)送到服務(wù)器的HTTP請求。 HttpResponse類的實例表示HTTP響應(yīng)。 WebSocket接口的實例表示一個WebSocket客戶端。 可以使用Java EE 7 WebSocket API創(chuàng)建WebSocket服務(wù)器。

使用構(gòu)建器創(chuàng)建HttpClientHttpRequestWebSocket的實例。 每個類型都包含一個名為Builder的嵌套類/接口,用于構(gòu)建該類型的實例。 請注意,不用創(chuàng)建HttpResponse,它作為所做的HTTP請求的一部分返回。 新的HTTP/2 Client API非常簡單,只需在一個語句中讀取HTTP資源! 以下代碼段使用GET請求,以URL https://www.google.com/作為字符串讀取內(nèi)容:

String responseBody = HttpClient.newHttpClient()
         .send(HttpRequest.newBuilder(new URI("https://www.google.com/"))
               .GET()
               .build(), BodyHandler.asString())
         .body();

處理HTTP請求的典型步驟如下:

  • 創(chuàng)建HTTP客戶端對象以保存HTTP配置信息。

  • 創(chuàng)建HTTP請求對象并使用要發(fā)送到服務(wù)器的信息進行填充。

  • 將HTTP請求發(fā)送到服務(wù)器。

  • 接收來自服務(wù)器的HTTP響應(yīng)對象作為響應(yīng)。

  • 處理HTTP響應(yīng)。

二. 設(shè)置案例

在本章中使用了許多涉及與Web服務(wù)器交互的例子。 不是使用部署在Internet上的Web應(yīng)用程序,而是在NetBeans中創(chuàng)建了一個可以在本地部署的Web應(yīng)用程序項目。 如果更喜歡使用其他Web應(yīng)用程序,則需要更改示例中使用的URL。

NetBeans Web應(yīng)用程序位于源代碼的webapp目錄中。 通過在GlassFish服務(wù)器4.1.1和Tomcat 8/9上部署Web應(yīng)用程序來測試示例。 可以從https://netbeans.org/下載帶有GlassFish服務(wù)器的NetBeans IDE。 在8080端口的GlassFish服務(wù)器上運行HTTP監(jiān)聽器。如果在另一個端口上運行HTTP監(jiān)聽器,則需要更改示例URL中的端口號。

本章的所有HTTP客戶端程序都位于com.jdojo.http.client模塊中,其聲明如下所示。

// module-info.javamodule com.jdojo.http.client {
    requires jdk.incubator.httpclient;
}

三. 創(chuàng)建HTTP客戶端

HTTP請求需要將配置信息發(fā)送到服務(wù)器,以便服務(wù)器知道要使用的身份驗證器,SSL配置詳細信息,要使用的cookie管理器,代理信息,服務(wù)器重定向請求時的重定向策略等。 HttpClient類的實例保存這些特定于請求的配置,它們可以重用于多個請求。 可以根據(jù)每個請求覆蓋其中的一些配置。 發(fā)送HTTP請求時,需要指定將提供請求的配置信息的HttpClient對象。 HttpClient包含用于所有HTTP請求的以下信息:驗證器,cookie管理器,執(zhí)行器,重定向策略,請求優(yōu)先級,代理選擇器,SSL上下文,SSL參數(shù)和HTTP版本。

認證者是java.net.Authenticator類的實例。 它用于HTTP身份驗證。 默認是不使用驗證器。

Cookie管理器用于管理HTTP Cookie。 它是java.net.CookieManager類的一個實例。 默認是不使用cookie管理器。

執(zhí)行器是java.util.concurrent.Executor接口的一個實例,用于發(fā)送和接收異步HTTP請求和響應(yīng)。 如果未指定,則提供默認執(zhí)行程序。

重定向策略是HttpClient.Redirect枚舉的常量,它指定如何處理服務(wù)器的重定向問題。 默認值NEVER,這意味著服務(wù)器發(fā)出的重定向不會被遵循。

請求優(yōu)先級是HTTP/2請求的默認優(yōu)先級,可以在1到256(含)之間。 這是服務(wù)器優(yōu)先處理請求的一個提示。 更高的值意味著更高的優(yōu)先級。

代理選擇器是java.net.ProxySelector類的一個實例,用于選擇要使用的代理服務(wù)器。 默認是不使用代理服務(wù)器。

SSL上下文是提供安全套接字協(xié)議實現(xiàn)的javax.net.ssl.SSLContext類的實例。當不需要指定協(xié)議或不需要客戶端身份驗證時, 提供了一個默認的SSLContext,此選項將起作用。

SSL參數(shù)是SSL/TLS/DTLS連接的參數(shù)。 它們保存在javax.net.ssl.SSLParameters類的實例中。

HTTP版本是HTTP的版本,它是1.1或2.它被指定為HttpClient.Version枚舉的常量:HTTP_1_1和HTTP_2。 它盡可能請求一個特定的HTTP協(xié)議版本。 默認值為HTTP_1_1。

Tips
HttpClient是不可變的。 當構(gòu)建這樣的請求時,存儲在HttpClient中的一些配置可能會被HTTP請求覆蓋。

HttpClient類是抽象的,不能直接創(chuàng)建它的對象。 有兩種方法可以創(chuàng)建一個HttpClient對象:

  • 使用HttpClient類的newHttpClient()靜態(tài)方法

  • 使用HttpClient.Builder類的build()方法

以下代碼段獲取默認的HttpClient對象:

// Get the default HttpClientHttpClient defaultClient = HttpClient.newHttpClient();

也可以使用HttpClient.Builder類創(chuàng)建HttpClient。 HttpClient.newBuilder()靜態(tài)方法返回一個新的HttpClient.Builder類實例。 HttpClient.Builder類提供了設(shè)置每個配置值的方法。 配置的值被指定為方法的參數(shù),該方法返回構(gòu)建器對象本身的引用,因此可以鏈接多個方法。 最后,調(diào)用返回HttpClient對象的build()方法。 以下語句創(chuàng)建一個HttpClient,重定向策略設(shè)置為ALWAYS,HTTP版本設(shè)置為HTTP_2:

// Create a custom HttpClient
HttpClient httpClient = HttpClient.newBuilder()                      .followRedirects(HttpClient.Redirect.ALWAYS)
                      .version(HttpClient.Version.HTTP_2)
                      .build();

HttpClient類包含對應(yīng)于每個配置設(shè)置的方法,該設(shè)置返回該配置的值。 這些方法如下:

Optional<Authenticator> authenticator()Optional<CookieManager> cookieManager()
Executor executor()
HttpClient.Redirect followRedirects()Optional<ProxySelector> proxy()
SSLContext sslContext()Optional<SSLParameters> sslParameters()
HttpClient.Version version()

請注意,HttpClient類中沒有setter方法,因為它是不可變的。 不能使用HttpClient自己本身的對象。 在使用HttpClient對象向服務(wù)器發(fā)送請求之前,需要使用HttpRequest對象。HttpClient類包含以下三種向服務(wù)器發(fā)送請求的方法:

<T> HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)<T> CompletableFuture<HttpResponse<T>> sendAsync(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)<U,T> CompletableFuture<U> sendAsync(HttpRequest req, HttpResponse.MultiProcessor<U,T> multiProcessor)

send()方法同步發(fā)送請求,而sendAsync()方法異步發(fā)送請求。

四. 處理HTTP請求

客戶端應(yīng)用程序使用HTTP請求與Web服務(wù)器進行通信。 它向服務(wù)器發(fā)送一個請求,服務(wù)器發(fā)回對應(yīng)的HTTP響應(yīng)。 HttpRequest類的實例表示HTTP請求。 以下是處理HTTP請求所需執(zhí)行的步驟:

  • 獲取HTTP請求構(gòu)建器(builder)

  • 設(shè)置請求的參數(shù)

  • 從構(gòu)建器創(chuàng)建HTTP請求

  • 將HTTP請求同步或異步發(fā)送到服務(wù)器

  • 處理來自服務(wù)器的響應(yīng)

1. 獲取HTTP請求構(gòu)建器

需要使用構(gòu)建器對象,該對象是HttpRequest.Builder類的實例來創(chuàng)建一個HttpRequest。 可以使用HttpRequest類的以下靜態(tài)方法獲取HttpRequest.Builder

HttpRequest.Builder newBuilder()HttpRequest.Builder newBuilder(URI uri)

以下代碼片段顯示了如何使用這些方法來獲取HttpRequest.Builder實例:

// A URI to point to googleURI googleUri = new URI("http://www.google.com");// Get a builder for the google URIHttpRequest.Builder builder1 = HttpRequest.newBuilder(googleUri);// Get a builder without specifying a URI at this timeHttpRequest.Builder builder2 = HttpRequest.newBuilder();

2. 設(shè)置HTTP請求參數(shù)

擁有HTTP請求構(gòu)建器后,可以使用構(gòu)建器的方法為請求設(shè)置不同的參數(shù)。 所有方法返回構(gòu)建器本身,因此可以鏈接它們。 這些方法如下:

HttpRequest.Builder DELETE(HttpRequest.BodyProcessor body)
HttpRequest.Builder expectContinue(boolean enable)
HttpRequest.Builder GET()
HttpRequest.Builder header(String name, String value)
HttpRequest.Builder headers(String... headers)
HttpRequest.Builder method(String method, HttpRequest.BodyProcessor body)
HttpRequest.Builder POST(HttpRequest.BodyProcessor body)
HttpRequest.Builder PUT(HttpRequest.BodyProcessor body)
HttpRequest.Builder setHeader(String name, String value)
HttpRequest.Builder timeout(Duration duration)
HttpRequest.Builder uri(URI uri)
HttpRequest.Builder version(HttpClient.Version version)

使用HttpClientHttpRequest發(fā)送到服務(wù)器。 當構(gòu)建HTTP請求時,可以使用version()方法通過HttpRequest.Builder對象設(shè)置HTTP版本值,該方法將在發(fā)送此請求時覆蓋HttpClient中設(shè)置的HTTP版本。 以下代碼片段將HTTP版本設(shè)置為2.0,以覆蓋默認HttpClient對象中的NEVER的默認值:

// By default a client uses HTTP 1.1. All requests sent using this// HttpClient will use HTTP 1.1 unless overridden by the requestHttpClient client = HttpClient.newHttpClient();        
// A URI to point to googleURI googleUri = new URI("http://www.google.com");// Get an HttpRequest that uses HTTP 2.0HttpRequest request = HttpRequest.newBuilder(googleUri)
                                 .version(HttpClient.Version.HTTP_2)
                                 .build();// The client object contains HTTP version as 1.1 and the request// object contains HTTP version 2.0. The following statement will// send the request using HTTP 2.0, which is in the request object.HttpResponse<String> r = clie
                                 http://www.cnblogs.com/IcanFixIt/p/7229611.html