Java HTTP客户端简介
HTTP客户端是在Java 11中添加的。它可以用于通过网络请求HTTP资源。它支持 HTTP / 1.1和HTTP / 2(同步和异步编程模型),将请求和响应主体作为反应流处理,并遵循熟悉的构建器模式。
示例:将响应主体打印为字符串的GET请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
HttpClient
要发送请求,请首先HttpClient
从其构建器创建一个。该构建器可用于配置每个客户端的状态,例如:
- 首选协议版本(HTTP / 1.1或HTTP / 2)
- 是否遵循重定向
- 代理人
- 认证者
HttpClient client = HttpClient.newBuilder()
.version(Version.HTTP_2)
.followRedirects(Redirect.SAME_PROTOCOL)
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.authenticator(Authenticator.getDefault())
.build();
构建完成后,HttpClient
可以用于发送多个请求。
HttpRequest
HttpRequest
从其生成器创建一个。请求构建器可用于设置:
- 请求URI
- 请求方法(GET,PUT,POST)
- 请求正文(如果有)
- 超时时间
- 请求头
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofFile(Paths.get("file.json")))
.build()
建成后,它HttpRequest
是不可变的,可以发送多次。
同步或异步
可以同步或异步发送请求。如预期的那样,同步API会阻塞,直到HttpResponse
可用为止 。
HttpResponse<String> response =
client.send(request, BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
异步API会立即返回 CompletableFuture
,并在HttpResponse
可用时完成 。 CompletableFuture
是在Java 8中添加的,并支持可组合的异步编程。
client.sendAsync(request, BodyHandlers.ofString())
.thenApply(response -> { System.out.println(response.statusCode());
return response; } )
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
数据作为反应流
请求和响应主体作为反应流(具有无阻塞背压的异步数据流)公开。HttpClient
有效地是请求主体的订阅者和响应主体字节的发布者。该 BodyHandler
接口允许在接收到实际的响应主体之前检查响应代码和标头,并负责创建响应 BodySubscriber
。
public abstract class HttpRequest {
...
public interface BodyPublisher
extends Flow.Publisher<ByteBuffer> { ... }
}
public abstract class HttpResponse<T> {
...
public interface BodyHandler<T> {
BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
}
public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> { ... }
}
的HttpRequest
和HttpResponse
类型,用于创建请求发布者和订阅者响应用于处理常见的类型,例如文件,字符串和字节提供了许多便利工厂方法。这些便利的实现要么累积数据,直到可以创建更高级别的Java类型(例如String),要么就文件流传输数据。的BodySubscriber
和 BodyPublisher
接口可以用于处理数据作为自定义反应性流来实现。
HttpRequest.BodyPublishers::ofByteArray(byte[])
HttpRequest.BodyPublishers::ofByteArrays(Iterable)
HttpRequest.BodyPublishers::ofFile(Path)
HttpRequest.BodyPublishers::ofString(String)
HttpRequest.BodyPublishers::ofInputStream(Supplier<InputStream>)
HttpResponse.BodyHandlers::ofByteArray()
HttpResponse.BodyHandlers::ofString()
HttpResponse.BodyHandlers::ofFile(Path)
HttpResponse.BodyHandlers::discarding()
以下是/的类型与HTTP客户端的/ 类型之间java.util.concurrent.Flow
的 适配器 :Publisher
Subscriber
BodyPublisher
BodySubscriber
HttpRequest.BodyPublishers::fromPublisher(...)
HttpResponse.BodyHandlers::fromSubscriber(...)
HttpResponse.BodyHandlers::fromLineSubscriber(...)
HTTP / 2
Java HTTP Client支持HTTP / 1.1和 HTTP / 2。默认情况下,客户端将使用HTTP / 2发送请求 。发送到尚不支持HTTP / 2的服务器的请求 将自动降级为HTTP / 1.1。以下是HTTP / 2 带来的主要改进的摘要:
- 标头压缩。HTTP / 2使用HPACK压缩,从而减少了开销。
- 与服务器的单一连接减少了建立多个TCP连接所需的往返次数。
- 多路复用。在同一连接上,同时允许多个请求。
- 服务器推送。可以将其他将来需要的资源发送给客户端。
- 二进制格式。更紧凑。
因为HTTP / 2是默认的首选协议,并且实现在必要时无缝回退到HTTP / 1.1,所以当HTTP / 2部署更广泛时,Java HTTP Client可以为将来做好准备。
参考文献
例子和食谱
以下是使用Java HTTP Client执行常见任务时可以遵循的许多示例和配方。有关Java HTTP Client的介绍,请参见 此处。
同步获取
响应主体为字符串
public void get(String uri) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
HttpResponse<String> response =
client.send(request, BodyHandlers.ofString());
System.out.println(response.body());
}
上面的示例使用ofString
BodyHandler
来将响应正文字节转换为 String
。BodyHandler
每个HttpRequest
发送的邮件都必须提供A。在 BodyHandler
决定如何处理响应体,如果有的话。
在BodyHandler
被调用一次响应状态代码和头文件是可用的,但接受主体的字节响应之前。的BodyHandler
是负责创建BodySubscriber
其是接收与非阻塞背压数据流的反应性流订户。的BodySubscriber
为可能的话,将所述响应体字节到更高级别的Java类型负责。
本HttpResponse.BodyHandlers
类提供了一些方便的静态工厂方法来创建一个 BodyHandler
。其中许多将响应字节存储在内存中,直到被完全接收为止,之后将其转换为更高级别的Java类型,例如 ofString
和ofByteArray
。其他人则在响应数据到达时进行流式处理。ofFile
, ofByteArrayConsumer
和ofInputStream
。或者,可以提供定制的书面订户实施。
响应主体作为文件
public void get(String uri) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
HttpResponse<Path> response =
client.send(request, BodyHandlers.ofFile(Paths.get("body.txt")));
System.out.println("Response in file:" + response.body());
}
异步获取
异步API会立即返回 CompletableFuture
,并在HttpResponse
可用时完成 。 CompletableFuture
是在Java 8中添加的,并支持可组合的异步编程。
响应主体为字符串
public CompletableFuture<String> get(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
该CompletableFuture.thenApply(Function)
方法可用于将映射HttpResponse
到其主体类型,状态代码等。
响应主体作为文件
public CompletableFuture<Path> get(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, BodyHandlers.ofFile(Paths.get("body.txt")))
.thenApply(HttpResponse::body);
}
发布
请求主体可以由提供 HttpRequest.BodyPublisher
。
public void post(String uri, String data) throws Exception {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.POST(BodyPublishers.ofString(data))
.build();
HttpResponse<?> response = client.send(request, BodyHandlers.discarding());
System.out.println(response.statusCode());
}
上面的示例使用将ofString
BodyPublisher
转换为给定的String
请求正文字节。
的BodyPublisher
是反应性流发布一个发布点播请求体的流。 HttpRequest.Builder
有许多方法可以设置BodyPublisher
; Builder::POST
, Builder::PUT
和Builder::method
。所述 HttpRequest.BodyPublishers
类具有许多创造一个方便静态工厂方法 BodyPublisher
用于普通类型的数据; ofString
,ofByteArray
, ofFile
。
该discarding
BodyHandler
可以用于接收和丢弃响应主体时,它是不感兴趣的。
并发请求
将Java Streams和CompletableFuture API组合在一起以发出大量请求并等待它们的响应很容易。以下示例为列表中的每个URI发送GET请求,并将所有响应存储为字符串。
public void getURIs(List<URI> uris) {
HttpClient client = HttpClient.newHttpClient();
List<HttpRequest> requests = uris.stream()
.map(HttpRequest::newBuilder)
.map(reqBuilder -> reqBuilder.build())
.collect(toList());
CompletableFuture.allOf(requests.stream()
.map(request -> client.sendAsync(request, ofString()))
.toArray(CompletableFuture<?>[]::new))
.join();
}
获取JSON
在许多情况下,响应主体将采用某种更高级别的格式。可以使用便捷响应正文处理程序以及第三方库将响应正文转换为该格式。
以下示例演示了如何结合使用杰克逊库和BodyHandlers::ofString
将JSON响应转换Map
为String键/值对。
public CompletableFuture<Map<String,String>> JSONBodyAsMap(URI uri) {
UncheckedObjectMapper objectMapper = new UncheckedObjectMapper();
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Accept", "application/json")
.build();
return HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(objectMapper::readValue);
}
class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
/** Parses the given JSON string into a Map. */
Map<String,String> readValue(String content) {
try {
return this.readValue(content, new TypeReference<>(){});
} catch (IOException ioe) {
throw new CompletionException(ioe);
}
}
上面的示例使用ofString
它将响应主体字节累积在内存中。替代地,ofInputStream
可以使用流订户。
发布JSON
在许多情况下,请求主体将采用某种更高级别的格式。可以使用便捷请求正文处理程序以及第三方库将请求正文转换为该格式。
以下示例演示了如何结合使用杰克逊库和 BodyPublishers::ofString
将Map
String键/值对转换为JSON。
public CompletableFuture<Void> postJSON(URI uri,
Map<String,String> map)
throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(requestBody))
.build();
return HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::statusCode)
.thenAccept(System.out::println);
}
设置代理
阿ProxySelector
可在被配置成 HttpClient
通过客户的 Builder::proxy
方法。该ProxySelector
API返回给定URI特定的代理。在许多情况下,一个静态代理就足够了。的 ProxySelector::of
静态工厂方法可以用于产生这样的选择器。
响应主体为带有指定代理的字符串
public CompletableFuture<String> get(String uri) {
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
或者,可以使用系统范围的默认代理选择器,这是macOS上的默认选择器。
HttpClient.newBuilder()
.proxy(ProxySelector.getDefault())
.build();