【Java】Exploring the New HTTP Client in Java
Part1探索 Java 中的新 HTTP 客戶端
Part2原文
https://www.baeldung.com/java-9-http-client
11. Overview
In this tutorial, we'll explore Java 11's standardization of?HTTP client API that implements HTTP/2 and Web Socket.
本文講討論Java 11 的新HTTP客戶端API是如何實(shí)現(xiàn) HTTP/2 和 WebSocket的。
It aims to replace the legacy?HttpUrlConnection?class that has been present in the JDK since the very early years of Java.
它旨在取代自 Java 誕生之初就存在于 JDK 中的傳統(tǒng)_HttpUrlConnection_ 類。
It aims to .... 旨在
Until very recently, Java provided only the?HttpURLConnection?API, which is low-level and isn't known for being feature-rich?and?user-friendly.
在舊版本中,Java 提供 HttpURLConnection API,該 API 是低級(jí)的,并不以功能豐富和用戶友好而著稱。
Therefore, some widely used third-party libraries were commonly used, such as?Apache HttpClient,?Jetty?and Spring's?RestTemplate.
所以,我們通常都會(huì)使用 類似 ??Apache HttpClient,?Jetty?或者 Spring's?RestTemplate 這樣的第三方庫作為替代。
2Further reading:(相關(guān)閱讀)
3Posting with Java HttpClient
From Java 9 onwards, the new HttpClient API provides both a synchronous and asynchronous modern web client. We look at how it can be used to make requests.
從 Java 9 開始,新的 HttpClient API 提供了同步和異步的現(xiàn)代 Web 客戶端。我們來看看如何使用它來發(fā)出請(qǐng)求。
Read more?→
4Java HttpClient With SSL
Learn how to use the Java HttpClient to connect to HTTPS URLs and also find out how to bypass certificate verification in non-production environments.
了解如何使用 Java HttpClient 連接 HTTPS URL,以及如何在非生產(chǎn)環(huán)境中繞過證書驗(yàn)證。
Read more?→
5Adding Parameters to Java HttpClient Requests
Different examples of HTTPClient core Java.
HTTPClient core Java 的不同示例。
Read more?→
62. Background
The change was implemented as a part of JEP 321.
所有的改變均實(shí)現(xiàn)自JEP 321。
2.1. Major Changes as Part of JEP 321
The incubated HTTP API from Java 9 is now officially incorporated into the Java SE API. The new?HTTP APIs?can be found in?java.net.HTTP.*
Java 9 中孵化的 HTTP API 現(xiàn)已正式納入 Java SE API。新的 HTTP APIs 可在 java.net.HTTP. 中找到。
The newer version of the HTTP protocol is designed to improve the overall performance of sending requests by a client and receiving responses from the server. This is achieved by introducing a number of changes such as stream multiplexing, header compression and push promises.
較新版本的 HTTP 協(xié)議旨在提高客戶端發(fā)送請(qǐng)求和服務(wù)器接收響應(yīng)的整體性能。這是通過引入流多路復(fù)用、報(bào)頭壓縮和推送承諾來實(shí)現(xiàn)的。
As of Java 11,?the API is now fully asynchronous (the previous HTTP/1.1 implementation was blocking).?Asynchronous calls are implemented using?CompletableFuture.The?CompletableFuture?implementation takes care of applying each stage once the previous one has finished, so this whole flow is asynchronous.
從 Java 11 開始,應(yīng)用程序接口現(xiàn)在是完全異步的(以前的 HTTP/1.1 實(shí)現(xiàn)是阻塞的)。 異步調(diào)用是使用 CompletableFuture 實(shí)現(xiàn)的。
The new HTTP client API provides a standard way to perform HTTP network operations with support for modern Web features such as HTTP/2, without the need to add third-party dependencies.
新的 HTTP 客戶端 API 提供了執(zhí)行 HTTP 網(wǎng)絡(luò)操作的標(biāo)準(zhǔn)方法,支持 HTTP/2 等現(xiàn)代網(wǎng)絡(luò)功能,無需添加第三方依賴性。
The new APIs provide native support for HTTP 1.1/2 WebSocket. The core classes and interface providing the core functionality include:
新的應(yīng)用程序接口為 HTTP 1.1/2
WebSocket 提供本地支持。提供核心功能的核心類和接口包括
The?HttpClient?class,?java.net.http.HttpClient
The?HttpRequest?class,?java.net.http.HttpRequest
The?HttpResponse< T > interface,?java.net.http.HttpResponse
The?WebSocket?interface,?java.net.http.WebSocket
HttpClient 類, java.net.http.HttpClient 。
HttpRequest 類,_java.net.http.HttpRequest_
接口_HttpResponse_< T >, ?java.net.http.HttpResponse
WebSocket 接口,_java.net.http.WebSocket_ < T >。
2.2. Problems With the Pre-Java 11 HTTP Client
The existing?HttpURLConnection?API and its implementation had numerous problems:
現(xiàn)有的 HttpURLConnection API 及其實(shí)現(xiàn)存在許多問題:
URLConnection API was designed with multiple protocols that are now no longer functioning (FTP, gopher, etc.).
The API predates HTTP/1.1 and is too abstract.
It works in blocking mode only (i.e., one thread per request/response).
It is very hard to maintain.
URLConnection API 在設(shè)計(jì)時(shí)使用了多個(gè)現(xiàn)已失效的協(xié)議(FTP、gopher 等)。
該 API 早于 HTTP/1.1,過于抽象。
只能在阻塞模式下工作(即每個(gè)請(qǐng)求/響應(yīng)只有一個(gè)線程)。
很難維護(hù)。
73. HTTP Client API Overview
Unlike?HttpURLConnection, HTTP Client provides synchronous and asynchronous request mechanisms.
與 HttpURLConnection 不同,HTTP 客戶端提供同步和異步請(qǐng)求機(jī)制。
The API consists of three core classes:
API 由三個(gè)核心類組成:
HttpRequest?represents the request to be sent via the?HttpClient.
HttpClient?behaves as a container for configuration information common to multiple requests.
HttpResponse?represents the result of an?HttpRequest?call.
HttpRequest 表示要通過 HttpClient 發(fā)送的請(qǐng)求。
HttpClient 是多個(gè)請(qǐng)求所共有的配置信息的容器。
HttpResponse 表示 HttpRequest 調(diào)用的結(jié)果。
We'll examine each of them in more details in the following sections. First, let's focus on a request.
我們將在下面的章節(jié)中對(duì)它們逐一進(jìn)行詳細(xì)介紹。首先,我們來關(guān)注一個(gè)請(qǐng)求。
84.?HttpRequest
HttpRequest?is an object that represents the request we want to send. New instances can be created using?HttpRequest.Builder.
HttpRequest 是一個(gè)對(duì)象,代表我們要發(fā)送的請(qǐng)求。可以使用 HttpRequest.Builder. 創(chuàng)建新實(shí)例。
We can get it by calling?HttpRequest.newBuilder().?Builder?class provides a bunch of methods that we can use to configure our request.
我們可以通過調(diào)用 HttpRequest.newBuilder() 來獲取它。?Builder 類提供了許多方法,我們可以用它們來配置我們的請(qǐng)求。
We'll cover the most important ones.
我們將介紹最重要的幾項(xiàng)。
Note: In JDK 16, there is a new?HttpRequest.newBuilder(HttpRequest request, BiPredicate<String,String> filter)?method, which creates a?Builder?whose initial state is copied from an existing?HttpRequest.
注意:在JDK16, 有一個(gè)新的 HttpRequest.newBuilder(HttpRequest request, BiPredicate<String,String> filter) ?方法,用于創(chuàng)建一個(gè)_Builder_,其初始狀態(tài)是從現(xiàn)有的_HttpRequest_復(fù)制而來。
This builder can be used to build an?HttpRequest, equivalent to the original, while allowing amendment of the request state prior to construction, for example, removing headers:
該構(gòu)建器可用于構(gòu)建一個(gè) _HttpRequest_,等同于原始請(qǐng)求,同時(shí)允許在構(gòu)建之前修改請(qǐng)求狀態(tài),例如刪除頭信息:
HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Foo-Bar"))
4.1. Setting?URI
The first thing we have to do when creating a request is to provide the URL.
創(chuàng)建請(qǐng)求時(shí),我們要做的第一件事就是提供 URL。
We can do that in two ways — using the constructor for?Builder?with?URI?parameter or calling?method?uri(URI)?on the?Builder?instance:
我們可以通過兩種方法實(shí)現(xiàn)這一目的:使用 URI 參數(shù)的 Builder 構(gòu)造函數(shù),或者調(diào)用 Builder 實(shí)例上的 uri(URI) 方法:
HttpRequest.newBuilder(new?URI("https://postman-echo.com/get"))
?
HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
The last thing we have to configure to create a basic request is an HTTP method.
創(chuàng)建基本請(qǐng)求的最后一項(xiàng)配置是 HTTP 方法。
4.2. Specifying the HTTP Method
We can define the HTTP method that our request will use by calling one of the methods from?Builder:
我們可以通過調(diào)用 Builder 中的一個(gè)方法來定義請(qǐng)求將使用的 HTTP 方法:
GET()
POST(BodyPublisher body)
PUT(BodyPublisher body)
DELETE()
We'll cover?BodyPublisher?in detail, later.
稍后我們將詳細(xì)介紹 BodyPublisher 的內(nèi)容。
Now let's just create?a very simple GET request example:
現(xiàn)在,讓我們創(chuàng)建個(gè)非常簡(jiǎn)單的 GET 請(qǐng)求示例:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.GET()
??.build();
This request has all parameters required by?HttpClient.
該請(qǐng)求包含 HttpClient 要求的所有參數(shù)。
However, we sometimes need to add additional parameters to our request. Here are some important ones:
不過,有時(shí)我們需要在請(qǐng)求中添加其他參數(shù)。下面是一些重要的參數(shù):
The version of the HTTP protocol
Headers
A timeout
HTTP 協(xié)議的版本
標(biāo)題
超時(shí)
4.3. Setting HTTP Protocol Version
The API fully leverages the HTTP/2 protocol and uses it by default, but we can define which version of the protocol we want to use:
該應(yīng)用程序接口完全利用 HTTP/2 協(xié)議,并默認(rèn)使用該協(xié)議,但我們可以定義要使用的協(xié)議版本:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.version(HttpClient.Version.HTTP_2)
??.GET()
??.build();
Important to mention here is that the client will fall back to, e.g., HTTP/1.1 if HTTP/2 isn't supported.
這里需要指出的是,如果不支持 HTTP/2,客戶端將退回到 HTTP/1.1 等協(xié)議。
4.4. Setting Headers
In case we want to add additional headers to our request, we can use the provided builder methods.
如果我們想在請(qǐng)求中添加其他標(biāo)頭,可以使用提供的構(gòu)建器方法。
We can do that by either passing all headers as key-value pairs to the?headers()?method or by using?header()?method for the single key-value header:
為此,我們可以將所有標(biāo)頭作為鍵值對(duì)傳遞給 headers() 方法,或者使用 header() 方法來處理單個(gè)鍵值標(biāo)頭:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.headers("key1",?"value1",?"key2",?"value2")
??.GET()
??.build();
HttpRequest?request2?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.header("key1",?"value1")
??.header("key2",?"value2")
??.GET()
??.build();
The last useful method we can use to customize our request is a?timeout().
我們可以用來定制請(qǐng)求的最后一個(gè)有用方法是 timeout() 。
4.5. Setting a Timeout
Let's now define the amount of time we want to wait for a response.
現(xiàn)在我們來定義等待響應(yīng)的時(shí)間。
If the set time expires, a?HttpTimeoutException?will be thrown. The default timeout is set to infinity.
如果設(shè)定的時(shí)間已過,就會(huì)拋出一個(gè) HttpTimeoutException 異常。默認(rèn)超時(shí)設(shè)置為無窮大。
The timeout can be set with the?Duration?object by calling method?timeout()?on the builder instance:
可以通過調(diào)用構(gòu)建器實(shí)例上的 timeout() 方法,使用 Duration 對(duì)象設(shè)置超時(shí)時(shí)間:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.timeout(Duration.of(10,?SECONDS))
??.GET()
??.build();
95. Setting a Request Body
We can add a body to a request by using the request builder methods:?POST(BodyPublisher body),?PUT(BodyPublisher body)?and?DELETE().
我們可以使用請(qǐng)求生成器方法為請(qǐng)求添加正文:?_POST(BodyPublisher body)、_PUT(BodyPublisher body) ?和 _DELETE()_。
The new API provides a number of?BodyPublisher?implementations out-of-the-box that simplify passing the request body:
新的 API 提供了許多開箱即用的 BodyPublisher 實(shí)現(xiàn),簡(jiǎn)化了請(qǐng)求正文的傳遞:
StringProcessor?– reads body from a?String, created with?HttpRequest.BodyPublishers.ofString
InputStreamProcessor?– reads body from an?InputStream, created with?HttpRequest.BodyPublishers.ofInputStream
ByteArrayProcessor?– reads body from a byte array, created with?HttpRequest.BodyPublishers.ofByteArray
FileProcessor?– reads body from a file at the given path, created with?HttpRequest.BodyPublishers.ofFile
StringProcessor - 從 String 中讀取正文,使用 HttpRequest.BodyPublishers.ofString 創(chuàng)建。
InputStreamProcessor - 從 InputStream 中讀取正文,使用 HttpRequest.BodyPublishers.ofInputStream 創(chuàng)建。
ByteArrayProcessor - 從字節(jié)數(shù)組中讀取正文,使用 HttpRequest.BodyPublishers.ofByteArray 創(chuàng)建。
FileProcessor - 從指定路徑的文件中讀取正文,使用 HttpRequest.BodyPublishers.ofFile 創(chuàng)建。
In case we don't need a body, we can simply pass in an?HttpRequest.BodyPublishers.__noBody():
如果不需要正文,我們只需傳入 HttpRequest.BodyPublishers. __noBody() ?即可:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/post"))
??.POST(HttpRequest.BodyPublishers.noBody())
??.build();
Note: In JDK 16, there's a new?HttpRequest.BodyPublishers.concat(BodyPublisher…)?method that helps us building a request body from the concatenation of the request bodies published by a sequence of publishers. The request body published by a?concatenation publisher?is logically equivalent to the request body that would have been published by concatenating all the bytes of each publisher in sequence.
注:在 JDK 16 中,有一個(gè)新的 HttpRequest.BodyPublishers.concat(BodyPublisher...) 方法,可以幫助我們通過串聯(lián)一系列發(fā)布者發(fā)布的請(qǐng)求體來構(gòu)建請(qǐng)求體。由 concatenation 發(fā)布者 發(fā)布的請(qǐng)求正文在邏輯上等同于按順序連接每個(gè)發(fā)布者的所有字節(jié)后發(fā)布的請(qǐng)求正文。
5.1.?StringBodyPublisher
Setting a request body with any?BodyPublishers?implementation is very simple and intuitive.
使用任何 BodyPublishers 實(shí)現(xiàn)來設(shè)置請(qǐng)求正文都非常簡(jiǎn)單直觀。
For example, if we want to pass a simple?String?as a body, we can use?StringBodyPublishers.
例如,如果我們想傳遞一個(gè)簡(jiǎn)單的 String 作為正文,我們可以使用 _StringBodyPublishers_。
As we already mentioned, this object can be created with a factory method?ofString()?— it takes just a?String?object as an argument and creates a body from it:
正如我們已經(jīng)提到的,可以使用工廠方法 ofString() 創(chuàng)建該對(duì)象 -- 該方法只接受一個(gè) String 對(duì)象作為參數(shù),并從中創(chuàng)建一個(gè)正文:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/post"))
??.headers("Content-Type",?"text/plain;charset=UTF-8")
??.POST(HttpRequest.BodyPublishers.ofString("Sample?request?body"))
??.build();
5.2.?InputStreamBodyPublisher
To do that, the?InputStream?has to be passed as a?Supplier?(to make its creation lazy), so it's a little bit different than?StringBodyPublishers.
要做到這一點(diǎn),必須將 InputStream 作為 Supplier 傳遞(變?yōu)閼屑虞d),因此它與 StringBodyPublishers 有點(diǎn)不同。
However, this is also quite straightforward:
不過,這也很簡(jiǎn)單明了:
byte[]?sampleData?=?"Sample?request?body".getBytes();
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/post"))
??.headers("Content-Type",?"text/plain;charset=UTF-8")
??.POST(HttpRequest.BodyPublishers
???.ofInputStream(()?->?new?ByteArrayInputStream(sampleData)))
??.build();
Notice how we used a simple?ByteArrayInputStream?here. Of course, that can be any?InputStream?implementation.
請(qǐng)注意我們?cè)谶@里使用了一個(gè)簡(jiǎn)單的 ByteArrayInputStream 。當(dāng)然,這可以是任何 InputStream 的實(shí)現(xiàn)。
5.3.?ByteArrayProcessor
We can also use?ByteArrayProcessor?and pass an array of bytes as the parameter:
我們還可以使用 ByteArrayProcessor 并將字節(jié)數(shù)組作為參數(shù)傳遞:
byte[]?sampleData?=?"Sample?request?body".getBytes();
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/post"))
??.headers("Content-Type",?"text/plain;charset=UTF-8")
??.POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
??.build();
5.4.?FileProcessor
To work with a File, we can make use of the provided?FileProcessor.、
要處理文件,我們可以使用所提供的 _FileProcessor_。
Its factory method takes a path to the file as a parameter and creates a body from the content:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/post"))
??.headers("Content-Type",?"text/plain;charset=UTF-8")
??.POST(HttpRequest.BodyPublishers.fromFile(
????Paths.get("src/test/resources/sample.txt")))
??.build();
We've covered how to create?HttpRequest?and how to set additional parameters in it.
我們已經(jīng)介紹了如何創(chuàng)建 HttpRequest 以及如何在其中設(shè)置附加參數(shù)。
Now it's time to take a deeper look at?HttpClient?class, which is responsible for sending requests and receiving responses.
現(xiàn)在是深入了解 HttpClient 類的時(shí)候了,它負(fù)責(zé)發(fā)送請(qǐng)求和接收響應(yīng)。
106.?HttpClient
All requests are sent using?HttpClient, which can be instantiated using the?HttpClient.newBuilder()?method or by calling?HttpClient.newHttpClient().
所有請(qǐng)求都是通過 HttpClient 發(fā)送的,可以使用 HttpClient.newBuilder() 方法或調(diào)用 HttpClient.newHttpClient() 來實(shí)例化 _HttpClient。
It provides a lot of useful and self-describing methods we can use to handle our request/response.
它提供了許多有用的自描述方法,我們可以用它們來處理請(qǐng)求/響應(yīng)。
Let's cover some of these here.
下面我們就來介紹其中的一些方法。
6.1. Handling Response Body
Similar to the fluent methods for creating publishers, there are methods dedicated to creating handlers for common body types:
與創(chuàng)建發(fā)布器的流暢方法類似,也有一些方法專門用于為常見的主體類型創(chuàng)建處理程序:
BodyHandlers.ofByteArray
BodyHandlers.ofString
BodyHandlers.ofFile
BodyHandlers.discarding
BodyHandlers.replacing
BodyHandlers.ofLines
BodyHandlers.fromLineSubscriber
Pay attention to the usage of the new?BodyHandlers?factory class.
請(qǐng)注意新的 BodyHandlers 工廠類的用法。
Before Java 11, we had to do something like this:
在 Java 11 之前,我們不得不這樣做:
HttpResponse<String>?response?=?client.send(request,?HttpResponse.BodyHandler.asString());
And we can now simplify it:
現(xiàn)在我們可以將其簡(jiǎn)化:
HttpResponse<String>?response?=?client.send(request,?BodyHandlers.ofString());
6.2. Setting a Proxy
We can define a proxy for the connection by just calling?proxy()?method on a?Builder?instance:
我們只需在 Builder 實(shí)例上調(diào)用 proxy() 方法,就能為連接定義一個(gè)代理:
HttpResponse<String>?response?=?HttpClient
??.newBuilder()
??.proxy(ProxySelector.getDefault())
??.build()
??.send(request,?BodyHandlers.ofString());
In our example, we used the default system proxy.
在我們的示例中,我們使用了默認(rèn)的系統(tǒng)代理。
6.3. Setting the Redirect Policy
Sometimes the page we want to access has moved to a different address.
有時(shí),我們想要訪問的頁面已經(jīng)轉(zhuǎn)移到了不同的地址。
In that case, we'll receive HTTP status code 3xx, usually with the information about new URI.?HttpClient?can redirect the request to the new URI automatically if we set the appropriate redirect policy.
在這種情況下,我們會(huì)收到 HTTP 狀態(tài)代碼 3xx,其中通常包含有關(guān)新 URI 的信息。?如果我們?cè)O(shè)置了適當(dāng)?shù)闹囟ㄏ虿呗裕?/strong>HttpClient 就能自動(dòng)將請(qǐng)求重定向到新的 URI。
We can do it with the?followRedirects()?method on?Builder:
我們可以通過 Builder 上的 followRedirects() 方法來實(shí)現(xiàn):
HttpResponse<String>?response?=?HttpClient.newBuilder()
??.followRedirects(HttpClient.Redirect.ALWAYS)
??.build()
??.send(request,?BodyHandlers.ofString());
All policies are defined and described in enum?HttpClient.Redirect.
所有策略都在枚舉 HttpClient.Redirect 中定義和描述。
6.4. Setting?Authenticator?for a Connection
An?Authenticator?is an object that negotiates credentials (HTTP authentication) for a connection.
驗(yàn)證器是一個(gè)為連接協(xié)商憑證(HTTP 驗(yàn)證)的對(duì)象。
It provides different authentication schemes (such as basic or digest authentication).
它提供不同的驗(yàn)證方案(如基本驗(yàn)證或摘要驗(yàn)證)。
In most cases, authentication requires username and password to connect to a server.
在大多數(shù)情況下,身份驗(yàn)證需要用戶名和密碼才能連接服務(wù)器。
We can use?PasswordAuthentication?class, which is just a holder of these values:
我們可以使用 PasswordAuthentication 類,它只是這些值的持有者:
HttpResponse<String>?response?=?HttpClient.newBuilder()
??.authenticator(new?Authenticator()?{
????
????protected?PasswordAuthentication?getPasswordAuthentication()?{
??????return?new?PasswordAuthentication(
????????"username",?
????????"password".toCharArray());
????}
}).build()
??.send(request,?BodyHandlers.ofString());
Here we passed the username and password values as a plaintext. Of course, this would have to be different in a production scenario.
在這里,我們以明文形式傳遞用戶名和密碼值。當(dāng)然,這在生產(chǎn)場(chǎng)景中必須有所不同。
Note that not every request should use the same username and password. The?Authenticator?class provides a number of?getXXX?(e.g.,?getRequestingSite()) methods that can be used to find out what values should be provided.
請(qǐng)注意,并非每個(gè)請(qǐng)求都應(yīng)使用相同的用戶名和密碼。_Authenticator_ 類提供了許多 _getXXX_(例如 _getRequestingSite()_)方法,可用于查找應(yīng)提供哪些值。
Now we're going to explore one of the most useful features of new?HttpClient?— asynchronous calls to the server.
現(xiàn)在,我們將探索新_HttpClient_最有用的功能之一--對(duì)服務(wù)器的異步調(diào)用。
6.5. Send Requests – Sync vs Async
New?HttpClient?provides two possibilities for sending a request to a server:
新的 HttpClient 提供了兩種向服務(wù)器發(fā)送請(qǐng)求的可能性:
send(…)?– synchronously?(blocks until the response comes)
sendAsync(…)?– asynchronously?(doesn't wait for the response, non-blocking)
send(...)-同步(阻塞直到響應(yīng)到來)
sendAsync(...)-異步(不等待響應(yīng),非阻塞)。
Up until now, the?send(...) method naturally waits for a response:
到目前為止,_send(._..) 方法一直在等待響應(yīng):
HttpResponse<String>?response?=?HttpClient.newBuilder()
??.build()
??.send(request,?BodyHandlers.ofString());
This call returns an?HttpResponse?object, and we're sure that the next instruction from our application flow will be run only when the response is already here.
該調(diào)用會(huì)返回一個(gè) HttpResponse 對(duì)象,我們可以確信,只有當(dāng)響應(yīng)已經(jīng)存在時(shí),應(yīng)用流程的下一條指令才會(huì)運(yùn)行。
However, it has a lot of drawbacks especially when we are processing large amounts of data.
不過,這種方法有很多缺點(diǎn),尤其是在處理大量數(shù)據(jù)時(shí)。
So, now we can use?sendAsync(...) method — which returns? CompletableFeature< HttpResponse> ?—?to process a request asynchronously:
因此,現(xiàn)在我們可以使用_sendAsync(._..)方法(該方法返回_CompletableFeature< HttpResponse>_)異步處理請(qǐng)求:
CompletableFuture<HttpResponse<String>>?response?=?HttpClient.newBuilder()
??.build()
??.sendAsync(request,?HttpResponse.BodyHandlers.ofString());
The new API can also deal with multiple responses, and stream the request and response bodies:
新的應(yīng)用程序接口還可以處理多個(gè)響應(yīng),并對(duì)請(qǐng)求和響應(yīng)體進(jìn)行流式處理:
List<URI>?targets?=?Arrays.asList(
??new?URI("https://postman-echo.com/get?foo1=bar1"),
??new?URI("https://postman-echo.com/get?foo2=bar2"));
HttpClient?client?=?HttpClient.newHttpClient();
List<CompletableFuture<String>>?futures?=?targets.stream()
??.map(target?->?client
????.sendAsync(
??????HttpRequest.newBuilder(target).GET().build(),
??????HttpResponse.BodyHandlers.ofString())
????.thenApply(response?->?response.body()))
??.collect(Collectors.toList());
6.6. Setting?Executor?for Asynchronous Calls
We can also define an?Executor?that provides threads to be used by asynchronous calls.
我們還可以定義一個(gè) Executor 來提供線程供異步調(diào)用使用。
This way we can, for example, limit the number of threads used for processing requests:
例如,這樣我們就可以限制用于處理請(qǐng)求的線程數(shù)量:
ExecutorService?executorService?=?Executors.newFixedThreadPool(2);
CompletableFuture<HttpResponse<String>>?response1?=?HttpClient.newBuilder()
??.executor(executorService)
??.build()
??.sendAsync(request,?HttpResponse.BodyHandlers.ofString());
CompletableFuture<HttpResponse<String>>?response2?=?HttpClient.newBuilder()
??.executor(executorService)
??.build()
??.sendAsync(request,?HttpResponse.BodyHandlers.ofString());
By default, the?HttpClient?uses executor?java.util.concurrent.Executors.newCachedThreadPool().
默認(rèn)情況下,_HttpClient_ 使用執(zhí)行器 _java.util.concurrent.Executors.newCachedThreadPool()_。
6.7. Defining a?CookieHandler
With new API and builder, it's straightforward to set a?CookieHandler?for our connection. We can use builder method?cookieHandler(CookieHandler cookieHandler)?to define client-specific?CookieHandler.
有了新的 API 和構(gòu)建器,為連接設(shè)置 CookieHandler 就變得簡(jiǎn)單易行了。
我們可以使用構(gòu)建器方法 cookieHandler(CookieHandler cookieHandler) 來定義客戶端特定的 _CookieHandler_。
Let's define?_CookieManager (_a concrete implementation of?CookieHandler?that separates the storage of cookies from the policy surrounding accepting and rejecting cookies) that doesn't allow to accept cookies at all:
讓我們定義 ?_CookieManager_( CookieHandler 的具體實(shí)現(xiàn),它將 Cookie 的存儲(chǔ)與接受和拒絕 Cookie 的策略分離開來),它完全不接受 Cookie:
HttpClient.newBuilder()
??.cookieHandler(new?CookieManager(null,?CookiePolicy.ACCEPT_NONE))
??.build();
In case our?CookieManager?allows cookies to be stored, we can access them by checking?CookieHandler?from our?HttpClient:
如果 CookieManager 允許存儲(chǔ) cookie,我們就可以通過檢查 HttpClient 中的 CookieHandler 來訪問它們:
((CookieManager)?httpClient.cookieHandler().get()).getCookieStore()
Now let's focus on the last class from Http API — the?HttpResponse.
現(xiàn)在,讓我們來關(guān)注 Http API 的最后一個(gè)類--_HttpResponse_。
117.?HttpResponse?Object
The?HttpResponse?class represents the response from the server. It provides a number of useful methods, but these are the two most important:
HttpResponse 類表示來自服務(wù)器的響應(yīng)。它提供了許多有用的方法,但其中最重要的有兩個(gè):
statusCode()?returns status code (type?int) for a response (HttpURLConnection?class contains possible values).
body()?returns a body for a response (return type depends on the response?BodyHandler?parameter passed to the?send()?method).
statusCode() 返回響應(yīng)的狀態(tài)代碼(注意類型 int_)(_HttpURLConnection 類包含可能的值)。
body() 返回響應(yīng)的正文(返回類型取決于傳遞給 send() 方法的響應(yīng) BodyHandler 參數(shù))。
The response object has other useful methods that we'll cover such as?uri(),?headers(),?trailers()?and?version().
響應(yīng)對(duì)象還有其他有用的方法,如 _uri()_、_headers()、_trailers() 和 _version()_。
7.1.?URI?of Response Object
The method?uri()?on the response object returns the?URI?from which we received the response.
響應(yīng)對(duì)象上的 uri() 方法會(huì)返回我們收到響應(yīng)的 URI 地址。
Sometimes it can be different than?URI?in the request object because a redirection may occur:
有時(shí)它可能與請(qǐng)求對(duì)象中的 URI 不同,因?yàn)榭赡軙?huì)發(fā)生重定向:
assertThat(request.uri()
??.toString(),?equalTo("http://stackoverflow.com"));
assertThat(response.uri()
??.toString(),?equalTo("https://stackoverflow.com/"));
7.2. Headers from Response
We can obtain headers from the response by calling method?headers()?on a response object:
我們可以通過調(diào)用響應(yīng)對(duì)象上的 headers() 方法來獲取響應(yīng)的標(biāo)題:
HttpResponse<String>?response?=?HttpClient.newHttpClient()
??.send(request,?HttpResponse.BodyHandlers.ofString());
HttpHeaders?responseHeaders?=?response.headers();
It returns?HttpHeaders?object, which represents a read-only view of HTTP Headers.
它返回 HttpHeaders 對(duì)象,該對(duì)象表示 HTTP 頭信息的只讀視圖。
It has some useful methods that simplify searching for headers value.
它有一些有用的方法,可以簡(jiǎn)化頭信息值的搜索。
7.3. Version of the Response
The method?version()?defines which version of HTTP protocol was used to talk with a server.
方法 version() 定義了與服務(wù)器通信時(shí)使用的 HTTP 協(xié)議版本。
Remember that even if we define that we want to use HTTP/2, the server can answer via HTTP/1.1.
請(qǐng)記住,即使我們定義要使用 HTTP/2,服務(wù)器也可以通過 HTTP/1.1 進(jìn)行應(yīng)答。
The version in which the server answered is specified in the response:
響應(yīng)中指定了服務(wù)器應(yīng)答的版本:
HttpRequest?request?=?HttpRequest.newBuilder()
??.uri(new?URI("https://postman-echo.com/get"))
??.version(HttpClient.Version.HTTP_2)
??.GET()
??.build();
HttpResponse<String>?response?=?HttpClient.newHttpClient()
??.send(request,?HttpResponse.BodyHandlers.ofString());
assertThat(response.version(),?equalTo(HttpClient.Version.HTTP_1_1));
128. Handling Push Promises in HTTP/2
New?HttpClient?supports push promises through?PushPromiseHandler?interface_._
新的 HttpClient 通過 PushPromiseHandler 接口支持推送承諾。
It allows the server to “push” content to the client additional resources while requesting the primary resource, saving more roundtrip and as a result improves performance in page rendering.
它允許服務(wù)器在請(qǐng)求主要資源的同時(shí)向客戶端 "推送 "附加資源內(nèi)容,從而節(jié)省了更多的往返時(shí)間,并因此提高了頁面渲染的性能。
It is really the multiplexing feature of HTTP/2 that allows us to forget about resource bundling. For each resource, the server sends a special request, known as a push promise to the client.
實(shí)際上,正是 HTTP/2 的多路復(fù)用功能讓我們忘記了資源捆綁。對(duì)于每個(gè)資源,服務(wù)器都會(huì)向客戶端發(fā)送一個(gè)特殊請(qǐng)求,即推送承諾。
Push promises received, if any, are handled by the given?PushPromiseHandler. A null valued?PushPromiseHandler?rejects any push promises.
收到的推送承諾(如果有)將由給定的 PushPromiseHandler 處理。空值_PushPromiseHandler_將拒絕任何推送承諾。
The?HttpClient?has an overloaded?sendAsync?method that allows us to handle such promises, as shown below.
如下所示,_HttpClient_ 有一個(gè)重載的 sendAsync 方法,允許我們處理此類承諾。
Let's first create a?PushPromiseHandler:
讓我們先創(chuàng)建一個(gè) PushPromiseHandler :
private?static?PushPromiseHandler<String>?pushPromiseHandler()?{
????return?(HttpRequest?initiatingRequest,?
????????HttpRequest?pushPromiseRequest,?
????????Function<HttpResponse.BodyHandler<String>,?
????????CompletableFuture<HttpResponse<String>>>?acceptor)?->?{
????????acceptor.apply(BodyHandlers.ofString())
????????????.thenAccept(resp?->?{
????????????????System.out.println("?Pushed?response:?"?+?resp.uri()?+?",?headers:?"?+?resp.headers());
????????????});
????????System.out.println("Promise?request:?"?+?pushPromiseRequest.uri());
????????System.out.println("Promise?request:?"?+?pushPromiseRequest.headers());
????};
}
Next, let's use?sendAsync?method to handle this push promise:
接下來,讓我們使用 sendAsync 方法來處理這個(gè)推送承諾:
httpClient.sendAsync(pageRequest,?BodyHandlers.ofString(),?pushPromiseHandler())
????.thenAccept(pageResponse?->?{
????????System.out.println("Page?response?status?code:?"?+?pageResponse.statusCode());
????????System.out.println("Page?response?headers:?"?+?pageResponse.headers());
????????String?responseBody?=?pageResponse.body();
????????System.out.println(responseBody);
????})
????.join();
139. Conclusion
In this article, we explored the Java 11?HttpClient?API that standardized the incubating HttpClient introduced in Java 9 with more powerful changes.
在本文中,我們探討了 Java 11 HttpClient API,它對(duì) Java 9 中引入的孵化 HttpClient 進(jìn)行了標(biāo)準(zhǔn)化,并做出了更強(qiáng)大的更改。
這篇文章討論了JDK11全新的HTTP API,
The complete code used can be found?over on GitHub.
使用的完整代碼可在 GitHub 上找到。
In the examples, we've used sample REST endpoints provided by?https://postman-echo.com.
在示例中,我們使用了 https://postman-echo.com 提供的 REST 端點(diǎn)示例。