爱看书的阿东

赐他一块白色石头,石头上写着新名

【Java】Exploring the New HTTP Client in Java

探索 Java 中的新 HTTP 客户端

原文

https://www.baeldung.com/java-9-http-client

1. 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是如何实现 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 中的传统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 是低级的,并不以功能丰富和用户友好而著称。

Therefore, some widely used third-party libraries were commonly used, such as Apache HttpClient, Jetty and Spring’s RestTemplate.

所以,我们通常都会使用 类似 Apache HttpClient, Jetty 或者 Spring’s RestTemplate 这样的第三方库作为替代。

Further reading:(相关阅读)

Posting 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 提供了同步异步的现代 Web 客户端。我们来看看如何使用它来发出请求。

Read more

Java 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,以及如何在非生产环境中绕过证书验证。

Read more

Adding Parameters to Java HttpClient Requests

Different examples of HTTPClient core Java.

HTTPClient core Java 的不同示例。

Read more

2. Background

The change was implemented as a part of JEP 321.

所有的改变均实现自JEP 321

2.1. Major Changes as Part of JEP 321

  1. 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 现已正式纳入 Java SE API。新的 HTTP APIs 可在 java.net.HTTP. 中找到。

  1. 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 协议旨在提高客户端发送请求和服务器接收响应的整体性能。这是通过引入流多路复用、报头压缩和推送承诺来实现的。

  1. 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 开始,应用程序接口现在是完全异步的(以前的 HTTP/1.1 实现是阻塞的)。 异步调用是使用 CompletableFuture 实现的。

  1. 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 提供了执行 HTTP 网络操作的标准方法,支持 HTTP/2 等现代网络功能,无需添加第三方依赖性。

  1. The new APIs provide native support for HTTP 1.1/2 WebSocket. The core classes and interface providing the core functionality include:

新的应用程序接口为 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:

现有的 HttpURLConnection API 及其实现存在许多问题:

  • 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 在设计时使用了多个现已失效的协议(FTP、gopher 等)。

  • 该 API 早于 HTTP/1.1,过于抽象。

  • 只能在阻塞模式下工作(即每个请求/响应只有一个线程)。

  • 很难维护。

    3. HTTP Client API Overview

Unlike HttpURLConnection, HTTP Client provides synchronous and asynchronous request mechanisms.

HttpURLConnection 不同,HTTP 客户端提供同步和异步请求机制。

The API consists of three core classes:

API 由三个核心类组成:

  • 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 发送的请求。

  • HttpClient 是多个请求所共有的配置信息的容器。

  • HttpResponse 表示 HttpRequest 调用的结果。

We’ll examine each of them in more details in the following sections. First, let’s focus on a request.

我们将在下面的章节中对它们逐一进行详细介绍。首先,我们来关注一个请求。

4. HttpRequest

HttpRequest is an object that represents the request we want to send. New instances can be created using HttpRequest.Builder.

HttpRequest 是一个对象,代表我们要发送的请求。可以使用 HttpRequest.Builder. 创建新实例。

We can get it by calling HttpRequest.newBuilder(). Builder class provides a bunch of methods that we can use to configure our request.

我们可以通过调用 HttpRequest.newBuilder() 来获取它。 Builder 类提供了许多方法,我们可以用它们来配置我们的请求。

We’ll cover the most important ones.

我们将介绍最重要的几项。

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, 有一个新的 HttpRequest.newBuilder(HttpRequest request, BiPredicate<String,String> filter) 方法,用于创建一个_Builder_,其初始状态是从现有的_HttpRequest_复制而来。

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:

该构建器可用于构建一个 _HttpRequest_,等同于原始请求,同时允许在构建之前修改请求状态,例如删除头信息:

1
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.

创建请求时,我们要做的第一件事就是提供 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:

我们可以通过两种方法实现这一目的:使用 URI 参数的 Builder 构造函数,或者调用 Builder 实例上的 uri(URI) 方法:

1
2
3
4
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.

创建基本请求的最后一项配置是 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:

我们可以通过调用 Builder 中的一个方法来定义请求将使用的 HTTP 方法:

  • GET()
  • POST(BodyPublisher body)
  • PUT(BodyPublisher body)
  • DELETE()

We’ll cover BodyPublisher in detail, later.

稍后我们将详细介绍 BodyPublisher 的内容。

Now let’s just create a very simple GET request example:

现在,让我们创建个非常简单的 GET 请求示例

1
2
3
4
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.GET()
.build();

This request has all parameters required by HttpClient.

该请求包含 HttpClient 要求的所有参数。

However, we sometimes need to add additional parameters to our request. Here are some important ones:

不过,有时我们需要在请求中添加其他参数。下面是一些重要的参数:

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:

该应用程序接口完全利用 HTTP/2 协议,并默认使用该协议,但我们可以定义要使用的协议版本:

1
2
3
4
5
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 等协议。

4.4. Setting Headers

In case we want to add additional headers to our request, we can use the provided builder methods.

如果我们想在请求中添加其他标头,可以使用提供的构建器方法。

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:

为此,我们可以将所有标头作为键值对传递给 headers() 方法,或者使用 header() 方法来处理单个键值标头:

1
2
3
4
5
6
7
8
9
10
11
12
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().

我们可以用来定制请求的最后一个有用方法是 timeout()

4.5. Setting a Timeout

Let’s now define the amount of time we want to wait for a response.

现在我们来定义等待响应的时间。

If the set time expires, a HttpTimeoutException will be thrown. The default timeout is set to infinity.

如果设定的时间已过,就会抛出一个 HttpTimeoutException 异常。默认超时设置为无穷大。

The timeout can be set with the Duration object by calling method timeout() on the builder instance:

可以通过调用构建器实例上的 timeout() 方法,使用 Duration 对象设置超时时间:

1
2
3
4
5
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.timeout(Duration.of(10, SECONDS))
.GET()
.build();

5. 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().

我们可以使用请求生成器方法为请求添加正文: 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 实现,简化了请求正文的传递:

  • 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 创建。

  • InputStreamProcessor - 从 InputStream 中读取正文,使用 HttpRequest.BodyPublishers.ofInputStream 创建。

  • ByteArrayProcessor - 从字节数组中读取正文,使用 HttpRequest.BodyPublishers.ofByteArray 创建。

  • FileProcessor - 从指定路径的文件中读取正文,使用 HttpRequest.BodyPublishers.ofFile 创建。

In case we don’t need a body, we can simply pass in an HttpRequest.BodyPublishers.__noBody():

如果不需要正文,我们只需传入 HttpRequest.BodyPublishers. __noBody() 即可:

1
2
3
4
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 中,有一个新的 HttpRequest.BodyPublishers.concat(BodyPublisher…) 方法,可以帮助我们通过串联一系列发布者发布的请求体来构建请求体。由 concatenation 发布者 发布的请求正文在逻辑上等同于按顺序连接每个发布者的所有字节后发布的请求正文。

5.1. StringBodyPublisher

Setting a request body with any BodyPublishers implementation is very simple and intuitive.

使用任何 BodyPublishers 实现来设置请求正文都非常简单直观。

For example, if we want to pass a simple String as a body, we can use StringBodyPublishers.

例如,如果我们想传递一个简单的 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:

正如我们已经提到的,可以使用工厂方法 ofString() 创建该对象 – 该方法只接受一个 String 对象作为参数,并从中创建一个正文:

1
2
3
4
5
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.

要做到这一点,必须将 InputStream 作为 Supplier 传递(变为懒加载),因此它与 StringBodyPublishers 有点不同。

However, this is also quite straightforward:

不过,这也很简单明了:

1
2
3
4
5
6
7
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.

请注意我们在这里使用了一个简单的 ByteArrayInputStream 。当然,这可以是任何 InputStream 的实现。

5.3. ByteArrayProcessor

We can also use ByteArrayProcessor and pass an array of bytes as the parameter:

我们还可以使用 ByteArrayProcessor 并将字节数组作为参数传递:

1
2
3
4
5
6
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:

1
2
3
4
5
6
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.

我们已经介绍了如何创建 HttpRequest 以及如何在其中设置附加参数。

Now it’s time to take a deeper look at HttpClient class, which is responsible for sending requests and receiving responses.

现在是深入了解 HttpClient 类的时候了,它负责发送请求和接收响应。

6. HttpClient

All requests are sent using HttpClient, which can be instantiated using the HttpClient.newBuilder() method or by calling HttpClient.newHttpClient().

所有请求都是通过 HttpClient 发送的,可以使用 HttpClient.newBuilder() 方法或调用 HttpClient.newHttpClient() 来实例化 _HttpClient。

It provides a lot of useful and self-describing methods we can use to handle our request/response.

它提供了许多有用的自描述方法,我们可以用它们来处理请求/响应。

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:

与创建发布器的流畅方法类似,也有一些方法专门用于为常见的主体类型创建处理程序:

1
2
3
4
5
6
7
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.

请注意新的 BodyHandlers 工厂类的用法。

Before Java 11, we had to do something like this:

在 Java 11 之前,我们不得不这样做:

1
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());

And we can now simplify it:

现在我们可以将其简化:

1
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 实例上调用 proxy() 方法,就能为连接定义一个代理:

1
2
3
4
5
HttpResponse<String> response = HttpClient
.newBuilder()
.proxy(ProxySelector.getDefault())
.build()
.send(request, BodyHandlers.ofString());

In our example, we used the default system proxy.

在我们的示例中,我们使用了默认的系统代理。

6.3. Setting the Redirect Policy

Sometimes the page we want to access has moved to a different address.

有时,我们想要访问的页面已经转移到了不同的地址。

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.

在这种情况下,我们会收到 HTTP 状态代码 3xx,其中通常包含有关新 URI 的信息。 如果我们设置了适当的重定向策略,HttpClient 就能自动将请求重定向到新的 URI。

We can do it with the followRedirects() method on Builder:

我们可以通过 Builder 上的 followRedirects() 方法来实现:

1
2
3
4
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.

验证器是一个为连接协商凭证(HTTP 验证)的对象。

It provides different authentication schemes (such as basic or digest authentication).

它提供不同的验证方案(如基本验证或摘要验证)。

In most cases, authentication requires username and password to connect to a server.

在大多数情况下,身份验证需要用户名和密码才能连接服务器。

We can use PasswordAuthentication class, which is just a holder of these values:

我们可以使用 PasswordAuthentication 类,它只是这些值的持有者:

1
2
3
4
5
6
7
8
9
10
HttpResponse<String> response = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
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.

在这里,我们以明文形式传递用户名和密码值。当然,这在生产场景中必须有所不同。

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.

请注意,并非每个请求都应使用相同的用户名和密码。Authenticator 类提供了许多 _getXXX_(例如 _getRequestingSite()_)方法,可用于查找应提供哪些值。

Now we’re going to explore one of the most useful features of new HttpClient — asynchronous calls to the server.

现在,我们将探索新_HttpClient_最有用的功能之一–对服务器的异步调用

6.5. Send Requests – Sync vs Async

New HttpClient provides two possibilities for sending a request to a server:

新的 HttpClient 提供了两种向服务器发送请求的可能性:

  • send(…) – synchronously (blocks until the response comes)

  • sendAsync(…) – asynchronously (doesn’t wait for the response, non-blocking)

  • send(…)-同步(阻塞直到响应到来)

  • sendAsync(…)-异步(不等待响应,非阻塞)。

Up until now, the send(...) method naturally waits for a response:

到目前为止,send(...) 方法一直在等待响应:

1
2
3
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.

该调用会返回一个 HttpResponse 对象,我们可以确信,只有当响应已经存在时,应用流程的下一条指令才会运行。

However, it has a lot of drawbacks especially when we are processing large amounts of data.

不过,这种方法有很多缺点,尤其是在处理大量数据时。

So, now we can use sendAsync(...) method — which returns CompletableFeature< HttpResponse>to process a request asynchronously:

因此,现在我们可以使用sendAsync(...)方法(该方法返回_CompletableFeature< HttpResponse>_)异步处理请求

1
2
3
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:

新的应用程序接口还可以处理多个响应,并对请求和响应体进行流式处理:

1
2
3
4
5
6
7
8
9
10
11
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.

我们还可以定义一个 Executor 来提供线程供异步调用使用。

This way we can, for example, limit the number of threads used for processing requests:

例如,这样我们就可以限制用于处理请求的线程数量:

1
2
3
4
5
6
7
8
9
10
11
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().

默认情况下,HttpClient 使用执行器 _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 和构建器,为连接设置 CookieHandler 就变得简单易行了。

我们可以使用构建器方法 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 的具体实现,它将 Cookie 的存储与接受和拒绝 Cookie 的策略分离开来),它完全不接受 Cookie:

1
2
3
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 允许存储 cookie,我们就可以通过检查 HttpClient 中的 CookieHandler 来访问它们:

1
((CookieManager) httpClient.cookieHandler().get()).getCookieStore()

Now let’s focus on the last class from Http API — the HttpResponse.

现在,让我们来关注 Http API 的最后一个类–_HttpResponse_。

7. 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 类表示来自服务器的响应。它提供了许多有用的方法,但其中最重要的有两个:

  • 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() 返回响应的状态代码(注意类型 int_)(_HttpURLConnection 类包含可能的值)。

  • body() 返回响应的正文(返回类型取决于传递给 send() 方法的响应 BodyHandler 参数)。

The response object has other useful methods that we’ll cover such as uri(), headers(), trailers() and version().

响应对象还有其他有用的方法,如 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.

响应对象上的 uri() 方法会返回我们收到响应的 URI 地址。

Sometimes it can be different than URI in the request object because a redirection may occur:

有时它可能与请求对象中的 URI 不同,因为可能会发生重定向:

1
2
3
4
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:

我们可以通过调用响应对象上的 headers() 方法来获取响应的标题:

1
2
3
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 对象,该对象表示 HTTP 头信息的只读视图。

It has some useful methods that simplify searching for headers value.

它有一些有用的方法,可以简化头信息值的搜索。

7.3. Version of the Response

The method version() defines which version of HTTP protocol was used to talk with a server.

方法 version() 定义了与服务器通信时使用的 HTTP 协议版本。

Remember that even if we define that we want to use HTTP/2, the server can answer via HTTP/1.1.

请记住,即使我们定义要使用 HTTP/2,服务器也可以通过 HTTP/1.1 进行应答

The version in which the server answered is specified in the response:

响应中指定了服务器应答的版本:

1
2
3
4
5
6
7
8
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));

8. 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.

它允许服务器在请求主要资源的同时向客户端 “推送 “附加资源内容,从而节省了更多的往返时间,并因此提高了页面渲染的性能。

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.

实际上,正是 HTTP/2 的多路复用功能让我们忘记了资源捆绑。对于每个资源,服务器都会向客户端发送一个特殊请求,即推送承诺。

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 有一个重载的 sendAsync 方法,允许我们处理此类承诺。

Let’s first create a PushPromiseHandler:

让我们先创建一个 PushPromiseHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
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 方法来处理这个推送承诺:

1
2
3
4
5
6
7
8
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();

9. 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,它对 Java 9 中引入的孵化 HttpClient 进行了标准化,并做出了更强大的更改。

这篇文章讨论了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 端点示例。