1. 概述

本文将深入探讨 Java 11 中标准化的 HTTP Client API,该 API 实现了 HTTP/2 和 WebSocket 协议。

它的目标是取代 JDK 中自 Java 早期版本就存在的老旧 HttpUrlConnection 类。

直到最近,Java 仅提供 HttpURLConnection API,这个 API 层级较低,且以功能贫瘠、用户体验差而闻名。

因此,开发者普遍依赖第三方库,例如:

2. 背景

该变更作为 JEP 321 的一部分实现。

2.1. JEP 321 的主要变更

  1. Java 9 中孵化的 HTTP API 现已正式纳入 Java SE API。新的 HTTP APIs 位于 java.net.http.* 包下
  2. 新版 HTTP 协议通过引入流多路复用、头部压缩和服务器推送等特性,显著提升了客户端请求与服务器响应的整体性能
  3. 自 Java 11 起,API 已完全异步化(之前的 HTTP/1.1 实现是阻塞式的)。异步调用通过 CompletableFuture 实现,其机制确保每个阶段在前一阶段完成后自动执行,整个流程完全异步
  4. 新 HTTP Client API 提供了执行 HTTP 网络操作的标准方式,原生支持 HTTP/2 等现代 Web 特性,无需第三方依赖
  5. 新 API 原生支持 HTTP 1.1/2 和 WebSocket。核心功能类包括:
    • HttpClient 类:java.net.http.HttpClient
    • HttpRequest 类:java.net.http.HttpRequest
    • HttpResponse 接口:java.net.http.HttpResponse
    • WebSocket 接口:java.net.http.WebSocket

2.2. Java 11 之前 HTTP Client 的问题

现有的 HttpURLConnection API 及其实现存在诸多缺陷:

  • URLConnection API 设计时考虑了多种现已失效的协议(FTP、gopher 等)
  • API 早于 HTTP/1.1 诞生,设计过于抽象
  • 仅支持阻塞模式(即每个请求/响应占用一个线程)
  • 维护成本极高

3. HTTP Client API 概览

HttpURLConnection 不同,HTTP Client 提供了同步和异步两种请求机制。

API 由三个核心类组成:

  • HttpRequest:表示要通过 HttpClient 发送的请求
  • HttpClient:作为多个请求共享的配置信息容器
  • HttpResponse:表示 HttpRequest 调用的结果

后续章节将详细解析这些组件。首先聚焦请求对象。

4. HttpRequest

HttpRequest 表示要发送的请求对象。新实例可通过 HttpRequest.Builder 创建。

调用 HttpRequest.newBuilder() 获取构建器。Builder 类提供丰富方法用于配置请求。

下面介绍关键配置项。

⚠️ JDK 16 新增了 newBuilder(HttpRequest request, BiPredicate<String,​String> filter) 方法,可基于现有请求创建构建器并过滤头部:

HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Foo-Bar"))

4.1. 设置 URI

创建请求的第一步是提供 URL。有两种方式:

  • 使用带 URI 参数的 Builder 构造函数
  • Builder 实例上调用 uri(URI) 方法:
HttpRequest.newBuilder(new URI("https://postman-echo.com/get"))
 
HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))

4.2. 指定 HTTP 方法

通过调用 Builder 的方法定义 HTTP 方法:

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

BodyPublisher 将在后续详细说明

简单 GET 请求示例

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

此请求包含 HttpClient 所需的所有基本参数。但通常需要添加额外配置:

  • HTTP 协议版本
  • 请求头
  • 超时设置

4.3. 设置 HTTP 协议版本

API 默认使用 HTTP/2 协议,但可显式指定版本:

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/get"))
  .version(HttpClient.Version.HTTP_2)
  .GET()
  .build();

重要提示:若服务器不支持 HTTP/2,客户端会自动降级到 HTTP/1.1

4.4. 设置请求头

通过构建器方法添加请求头:

  • 使用 headers() 方法批量添加键值对
  • 使用 header() 方法逐个添加:
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();

4.5. 设置超时

通过 timeout() 方法定义等待响应的时间。超时将抛出 HttpTimeoutException,默认超时为无限:

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

5. 设置请求体

通过请求构建器方法添加请求体:

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

新 API 提供多种开箱即用的 BodyPublisher 实现:

  • StringProcessor:从字符串读取体,通过 HttpRequest.BodyPublishers.ofString 创建
  • InputStreamProcessor:从输入流读取体,通过 HttpRequest.BodyPublishers.ofInputStream 创建
  • ByteArrayProcessor:从字节数组读取体,通过 HttpRequest.BodyPublishers.ofByteArray 创建
  • FileProcessor:从文件读取体,通过 HttpRequest.BodyPublishers.ofFile 创建

无需请求体时使用 HttpRequest.BodyPublishers.noBody()

HttpRequest request = HttpRequest.newBuilder()
  .uri(new URI("https://postman-echo.com/post"))
  .POST(HttpRequest.BodyPublishers.noBody())
  .build();

⚠️ JDK 16 新增 BodyPublishers.concat(BodyPublisher…) 方法,可将多个发布者的请求体串联

5.1. StringBodyPublisher

使用 StringBodyPublishers 设置字符串请求体非常直观:

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

需将 InputStream 作为 Supplier 传递(实现延迟创建):

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();

此处使用 ByteArrayInputStream,实际可替换为任何 InputStream 实现

5.3. ByteArrayProcessor

直接传递字节数组:

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

使用文件路径创建请求体:

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();

6. HttpClient

所有请求通过 HttpClient 发送,可通过 HttpClient.newBuilder()HttpClient.newHttpClient() 实例化。

6.1. 处理响应体

类似发布者的工厂方法,API 提供常用响应体类型的处理器:

BodyHandlers.ofByteArray
BodyHandlers.ofString
BodyHandlers.ofFile
BodyHandlers.discarding
BodyHandlers.replacing
BodyHandlers.ofLines
BodyHandlers.fromLineSubscriber

✅ 注意新的 BodyHandlers 工厂类简化了代码: Java 11 之前

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

**Java 11+**:

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

6.2. 设置代理

通过 proxy() 方法配置代理:

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

示例使用系统默认代理

6.3. 设置重定向策略

当页面迁移时,服务器返回 3xx 状态码和新 URI。HttpClient 可根据配置自动重定向

HttpResponse<String> response = HttpClient.newBuilder()
  .followRedirects(HttpClient.Redirect.ALWAYS)
  .build()
  .send(request, BodyHandlers.ofString());

所有策略定义在枚举 HttpClient.Redirect

6.4. 设置连接认证器

Authenticator 处理连接的凭据协商(HTTP 认证),支持多种认证方案(如 Basic/Digest 认证):

HttpResponse<String> response = HttpClient.newBuilder()
  .authenticator(new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
      return new PasswordAuthentication(
        "admin", 
        "s3cr3t".toCharArray());
    }
}).build()
  .send(request, BodyHandlers.ofString());

⚠️ 生产环境中应避免硬编码凭据,Authenticator 提供的 getRequestingSite() 等方法可动态获取认证信息

6.5. 发送请求 – 同步 vs 异步

HttpClient 提供两种请求方式:

  • send(…) – 同步(阻塞直到响应返回)
  • sendAsync(…) – 异步(非阻塞,立即返回)

同步请求示例

HttpResponse<String> response = HttpClient.newBuilder()
  .build()
  .send(request, BodyHandlers.ofString());

异步请求示例

CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
  .build()
  .sendAsync(request, HttpResponse.BodyHandlers.ofString());

✅ 异步 API 支持多响应处理和流式传输:

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. 为异步调用设置执行器

通过 executor() 方法自定义线程池,限制并发请求数:

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());

默认使用 java.util.concurrent.Executors.newCachedThreadPool()

6.7. 定义 CookieHandler

通过 cookieHandler() 方法配置 Cookie 策略:

HttpClient.newBuilder()
  .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
  .build();

允许存储 Cookie 时,可通过以下方式访问:

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

7. HttpResponse 对象

HttpResponse 表示服务器响应,关键方法包括:

  • statusCode():返回状态码(int 类型)
  • body():返回响应体(类型取决于 BodyHandler

其他实用方法:uri()headers()trailers()version()

7.1. 响应对象的 URI

uri() 返回实际响应的 URI(可能因重定向与请求 URI 不同):

assertThat(request.uri()
  .toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri()
  .toString(), equalTo("https://stackoverflow.com/"));

7.2. 响应头部

通过 headers() 获取只读的 HttpHeaders 对象:

HttpResponse<String> response = HttpClient.newHttpClient()
  .send(request, HttpResponse.BodyHandlers.ofString());
HttpHeaders responseHeaders = response.headers();

7.3. 响应协议版本

version() 返回实际通信使用的 HTTP 版本(即使请求指定 HTTP/2,服务器可能用 HTTP/1.1 响应):

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, BodyHandlers.ofString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));

8. 处理 HTTP/2 的服务器推送

HttpClient 通过 PushPromiseHandler 接口支持服务器推送。

核心优势:服务器在响应主资源时主动推送额外资源,减少往返延迟,提升页面渲染性能

HttpClient 提供重载的 sendAsync 方法处理推送承诺:

创建 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());
    };
}

处理推送承诺

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. 总结

本文深入解析了 Java 11 标准化的 HttpClient API,该 API 在 Java 9 孵化版本基础上进行了重大增强。

完整示例代码可在 GitHub 获取。

文中示例使用了 https://postman-echo.com 提供的测试接口。


原始标题:Exploring the New HTTP Client in Java

« 上一篇: Java中的观察者模式
» 下一篇: Java 享元模式详解