跳转至

HTTP Client#

版本信息

为什么需要#

JDK 8 的 HttpURLConnection 是 1990 年代的 API,写一个 GET 都很啰嗦:openConnection() + 强转 + setRequestMethod + 手动读 InputStream 拼字符串 + disconnect(),配置头/超时要一个个 setRequestProperty,还不支持 HTTP/2,异步得自己开线程池包。JEP 321 把孵化过的 HTTP Client 标准化进 java.net.http:流式构建器、不可变请求对象、内置 HTTP/2(含多路复用、服务器推送)、WebSocket、同步与异步(CompletableFuture)双形态,API 干净现代。

语法#

examples/http-client/真实可运行的源码(snippets 嵌入,与示例零漂移)——demo 用 JDK 内置 com.sun.net.httpserver 起本地服务,自包含、不依赖外部网络:

package com.javamodern.httpclient;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

// JEP 321(JDK 11):标准化 HTTP Client(java.net.http)
// —— 取代老旧、笨重的 HttpURLConnection,支持 HTTP/2、WebSocket、同步与异步。
public class HttpClientDemo {

    /** 同步 GET:构建请求 → 阻塞发送 → 返回响应体字符串。 */
    public static String getBody(HttpClient client, URI uri) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()                 // (1) 构建不可变请求
                .uri(uri)
                .GET()
                .build();
        HttpResponse<String> response =
                client.send(request, BodyHandlers.ofString());        // (2) 同步发送,BodyHandler 决定如何消费响应体
        return response.body();
    }

    public static void main(String[] args) throws Exception {
        // 用 JDK 内置 com.sun.net.httpserver 起一个本地服务,使 demo 自包含、不依赖外部网络
        var server = com.sun.net.httpserver.HttpServer.create(
                new java.net.InetSocketAddress("127.0.0.1", 0), 0);
        server.createContext("/", exchange -> {
            byte[] body = "Hello, Java 11!".getBytes(java.nio.charset.StandardCharsets.UTF_8);
            exchange.sendResponseHeaders(200, body.length);
            try (var os = exchange.getResponseBody()) {
                os.write(body);
            }
        });
        server.start();
        try {
            URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/");
            HttpClient client = HttpClient.newHttpClient();           // HTTP/2-capable;对明文 HTTP 走 HTTP/1.1
            System.out.println("body = " + getBody(client, uri));
        } finally {
            server.stop(0);
        }
    }
}
  1. :material-lightbulb: HttpRequest.newBuilder().uri(...).GET().build() 流式构建不可变请求;client.send(req, BodyHandlers.ofString()) 同步发送,BodyHandler 决定响应体如何消费(字符串、文件、字节数组……)。

运行 HttpClientDemo.main 的实测输出(JDK 11,本地服务):

body = Hello, Java 11!

异步形态(无需阻塞,返回 CompletableFuture,本页略运行):

client.sendAsync(req, BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println);

与 JDK 8 旧写法对比#

URL url = new URL("http://example.com/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
StringBuilder body = new StringBuilder();
try (var r = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
    String line;
    while ((line = r.readLine()) != null) body.append(line);
}
conn.disconnect();   // 不支持 HTTP/2,异步要自己包线程池
HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(5))
        .build();
HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/"))
        .timeout(Duration.ofSeconds(10))
        .GET().build();
HttpResponse<String> resp = client.send(req, BodyHandlers.ofString());
String body = resp.body();          // 或 client.sendAsync(...) 走 CompletableFuture

底层原理#

HttpClient 是核心、不可变且线程安全,可全局复用一个实例。HttpRequest 不可变、用构建器创建。发送分两条路:send 同步阻塞返回 HttpResponsesendAsync 返回 CompletableFuture<HttpResponse>,回调链式组合、由 HttpClient 内部的线程池驱动。HTTP/2 下多个请求可多路复用到同一连接(HttpClient 自动连接复用与协议协商),HTTP/1.1 则按需。

graph LR
    C["HttpClient(不可变、线程安全、可复用)"] -->|send 同步| R1["HttpResponse"]
    C -->|sendAsync 异步| F["CompletableFuture<HttpResponse>"]
    Req["HttpRequest(构建器、不可变)"] --> C
    C -.HTTP/2 多路复用/协议协商.- H2["连接池"]
    style C fill:#bbdefb
    style Req fill:#c8e6c9

常见坑 / 最佳实践#

  • 复用 HttpClient:它构建成本不低(含线程池/连接池),别每次请求 newHttpClient();做成单例/字段复用。
  • HTTP/2 通常要 HTTPS:浏览器与服务器普遍只在 TLS(ALPN 协商)上跑 HTTP/2;明文 http:// 通常回退 HTTP/1.1。.version(HTTP_2) 是「偏好」,协商不成自动降级。
  • BodyHandler 选对ofString/ofByteArray/ofFile/ofInputStream/ofLines(流式)——大响应别一股脑 ofString 吃进内存。
  • 超时两层HttpClient.connectTimeout(建连)+ HttpRequest.timeout(整体),都要设,否则可能挂死。
  • 异步别忘错误分支sendAsyncCompletableFuture 链补 .exceptionally(...) / handle,否则异常被吞。

小结#

HttpClient 是 JDK 11 标准库里最「现代化」的一块:替换掉 HttpURLConnection 的陈旧与啰嗦,原生支持 HTTP/2、WebSocket 与异步,让 Java 自带一个拿得出手的 HTTP 客户端,无需再依赖三方库。

参考#