1. 概述

Java 11 引入了Java HttpClient API,实现了最新HTTP标准的客户端部分。它支持HTTP/1.1和HTTP/2,提供了同步和异步编程模型。

我们可以使用它发送HTTP请求并获取响应。在Java 11之前,我们不得不依赖原始的URLConnection实现,或第三方库如Apache HttpClient

在这个教程中,我们将学习如何使用Java HttpClient发送POST请求。我们将展示如何发送同步、异步以及并发POST请求,并查看如何添加身份验证参数和JSON。最后,我们将了解如何上传文件和提交表单数据,从而覆盖大部分常见用例。

2. 准备POST请求

在发送HTTP请求之前,我们需要先创建一个HttpClient实例。

HttpClient实例可以通过其构建器进行配置和创建,使用newBuilder方法。如果没有特殊配置需求,我们可以使用newHttpClient实用方法创建一个默认客户端:

HttpClient client = HttpClient.newHttpClient();

HttpClient默认使用HTTP/2,如果服务器不支持HTTP/2,它会自动降级到HTTP/1.1。

现在,我们准备好使用构建器创建一个HttpRequest实例。稍后我们会使用这个客户端实例发送请求。POST请求的最小参数包括服务器URL、请求方法和主体:

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create(serviceUrl))
  .POST(HttpRequest.BodyPublishers.noBody())
  .build();

请求主体需要通过BodyPublisher类提供。这是一个响应式流发布者,按需发布请求体的流。在我们的示例中,我们使用了一个不发送请求主体的body发布者。

3. 发送POST请求

现在我们已经准备好了POST请求,让我们看看发送它的不同方式。

3.1. 同步请求

我们可以使用默认的send方法发送准备好的请求。这个方法将阻塞代码直到收到响应

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString())

BodyHandlers实用工具实现了各种有用的处理器,例如将响应体作为String处理或将响应体流式写入文件。一旦收到响应,HttpResponse对象将包含响应状态、头和体:

assertThat(response.statusCode())
  .isEqualTo(200);
assertThat(response.body())
  .isEqualTo("{\"message\":\"ok\"}");

3.2. 异步请求

我们可以使用sendAsync方法异步发送上一个示例中的相同请求。与同步方法不同,这个方法会立即返回一个CompletableFuture实例

CompletableFuture<HttpResponse<String>> futureResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

一旦CompletableFuture可用,它将完成并返回HttpResponse

HttpResponse<String> response = futureResponse.get();
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");

3.3. 并发请求

我们可以结合StreamCompletableFuture并发发送多个请求并等待它们的响应

List<CompletableFuture<HttpResponse<String>>> completableFutures = serviceUrls.stream()
  .map(URI::create)
  .map(HttpRequest::newBuilder)
  .map(builder -> builder.POST(HttpRequest.BodyPublishers.noBody()))
  .map(HttpRequest.Builder::build)
  .map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
  .collect(Collectors.toList());

现在,让我们等待所有请求完成,以便一次处理它们的响应:

CompletableFuture<List<HttpResponse<String>>> combinedFutures = CompletableFuture
  .allOf(completableFutures.toArray(new CompletableFuture[0]))
  .thenApply(future ->
    completableFutures.stream()
      .map(CompletableFuture::join)
      .collect(Collectors.toList()));

由于我们使用了allOfjoin方法组合所有响应,我们得到了一个新的CompletableFuture,其中包含了我们的响应:

List<HttpResponse<String>> responses = combinedFutures.get();
responses.forEach((response) -> {
  assertThat(response.statusCode()).isEqualTo(200);
  assertThat(response.body()).isEqualTo("{\"message\":\"ok\"}");
});

4. 添加身份验证参数

我们可以在客户端级别设置一个身份验证器,以便在所有请求中进行HTTP身份验证

HttpClient client = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        "baeldung",
        "123456".toCharArray());
      }
  })
  .build();

然而,HttpClient不会在服务器发送带有WWW-Authenticate头的挑战时自动发送基本凭据。

为了绕过这个问题,我们可以始终手动创建并发送Authorization请求头:

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create(serviceUrl))
  .POST(HttpRequest.BodyPublishers.noBody())
  .header("Authorization", "Basic " + 
    Base64.getEncoder().encodeToString(("baeldung:123456").getBytes()))
  .build();

5. 添加请求体

5.1. JSON请求

使用 BodyPublishers 工具类可以很方便的发送各种数据类型,从String类型到文件上传。我们可以将JSON数据作为字符串类型发送:

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create(serviceUrl))
  .POST(HttpRequest.BodyPublishers.ofString("{\"action\":\"hello\"}"))
  .build();

5.2. 上传文件

让我们创建一个临时文件,以便通过HttpClient上传:

Path file = tempDir.resolve("temp.txt");
List<String> lines = Arrays.asList("1", "2", "3");
Files.write(file, lines);

HttpClient提供了单独的方法BodyPublishers.ofFile,用于将文件添加到POST请求体中:

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create(serviceUrl))
  .POST(HttpRequest.BodyPublishers.ofFile(file))
  .build();

5.3. Form表单

与文件不同,HttpClient没有提供专门用于提交表单数据的方法。因此,我们仍然需要使用BodyPublishers.ofString方法

Map<String, String> formData = new HashMap<>();
formData.put("username", "baeldung");
formData.put("message", "hello");

HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create(serviceUrl))
  .header("Content-Type", "application/x-www-form-urlencoded")
  .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
  .build();

但是,我们需要将表单数据从Map转换为String,使用自定义实现:

private static String getFormDataAsString(Map<String, String> formData) {
    StringBuilder formBodyBuilder = new StringBuilder();
    for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
        if (formBodyBuilder.length() > 0) {
            formBodyBuilder.append("&");
        }
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
        formBodyBuilder.append("=");
        formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
    }
    return formBodyBuilder.toString();
}

6. 总结

在这篇文章中,我们探讨了Java 11中引入的Java HttpClient API发送POST请求的方法

我们学习了如何创建HttpClient实例和准备POST请求。我们看到了如何同步、异步和并发发送准备好的请求。接着,我们也了解了如何添加基本身份验证参数。

最后,我们研究了如何向POST请求添加主体。我们涵盖了JSON负载、文件上传和表单提交。

如往常一样,完整的源代码可在GitHub上找到。