Skip to content

HTTP Client API

JDK 11 之前,如果你想发一个 HTTP 请求,通常需要:

java
// 方式一:用 HttpURLConnection(难用)
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// ... 一堆样板代码

// 方式二:用第三方库
HttpClient client = HttpClientBuilder.create().build();  // Apache HttpClient
// 依赖又多一个

JDK 11 引入了标准 HTTP Client API,内置支持 HTTP/1.1 和 HTTP/2,同步和异步都能搞定。

快速上手

发送 GET 请求

java
import java.net.URI;
import java.net.http.*;

public class HttpClientDemo {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
            .GET()
            .build();

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

        System.out.println("状态码: " + response.statusCode());
        System.out.println("响应体: " + response.body());
    }
}

发送 POST 请求

java
HttpClient client = HttpClient.newHttpClient();

String requestBody = """
    {
        "title": "Hello",
        "body": "World",
        "userId": 1
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();

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

System.out.println(response.statusCode());
System.out.println(response.body());

核心组件

HttpClient

java
import java.net.http.*;

// 创建 HttpClient
HttpClient client = HttpClient.newHttpClient();

// 自定义配置
HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)    // HTTP/2(默认)
    // .version(HttpClient.Version.HTTP_1_1)  // 强制 HTTP/1.1
    .connectTimeout(Duration.ofSeconds(10))
    .followRedirects(HttpClient.Redirect.NORMAL)
    .build();

HttpRequest

java
// GET 请求
HttpRequest getRequest = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/data"))
    .GET()
    .build();

// POST 请求
HttpRequest postRequest = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/submit"))
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();

// PUT 请求
HttpRequest putRequest = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/resource/1"))
    .header("Content-Type", "application/json")
    .PUT(HttpRequest.BodyPublishers.ofString(requestBody))
    .build();

// DELETE 请求
HttpRequest deleteRequest = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/resource/1"))
    .DELETE()
    .build();

异步请求

java
import java.net.http.*;
import java.util.concurrent.*;

public class AsyncDemo {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
            .GET()
            .build();

        // 异步发送
        CompletableFuture<HttpResponse<String>> future =
            client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        // 注册回调
        future.thenApply(HttpResponse::body)
            .thenAccept(body -> System.out.println("响应: " + body))
            .exceptionally(e -> {
                System.err.println("请求失败: " + e.getMessage());
                return null;
            });

        // 主线程继续做其他事
        System.out.println("主线程继续执行...");

        // 等待异步完成
        Thread.sleep(2000);
    }
}

并发请求

java
import java.net.http.*;
import java.util.*;

public class ConcurrentDemo {

    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        List<String> urls = List.of(
            "https://jsonplaceholder.typicode.com/posts/1",
            "https://jsonplaceholder.typicode.com/posts/2",
            "https://jsonplaceholder.typicode.com/posts/3"
        );

        List<HttpRequest> requests = urls.stream()
            .map(url -> HttpRequest.newBuilder()
                .uri(URI.create(url))
                .GET()
                .build())
            .toList();

        // 并发发送所有请求
        List<CompletableFuture<HttpResponse<String>>> futures = requests.stream()
            .map(req -> client.sendAsync(req, HttpResponse.BodyHandlers.ofString()))
            .toList();

        // 等待所有请求完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 收集结果
        for (int i = 0; i < futures.size(); i++) {
            HttpResponse<String> response = futures.get(i).get();
            System.out.println("URL " + (i + 1) + ": " + response.statusCode());
        }
    }
}

处理响应

响应体类型

java
// String
HttpResponse<String> stringResponse = client.send(request,
    HttpResponse.BodyHandlers.ofString());

// ByteArray
HttpResponse<byte[]> bytesResponse = client.send(request,
    HttpResponse.BodyHandlers.ofByteArray());

// File
HttpResponse<Void> fileResponse = client.send(request,
    HttpResponse.BodyHandlers.ofFile(Path.of("response.json")));

// Discard(只关心状态码)
HttpResponse<Void> voidResponse = client.send(request,
    HttpResponse.BodyHandlers.discarding());

// 自定义
HttpResponse<MyObject> customResponse = client.send(request,
    HttpResponse.BodyHandlers.ofMapping(body -> parseJson(body)));

响应头

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

// 获取单个头
String contentType = response.headers().firstValue("Content-Type").orElse("");
String date = response.headers().firstValue("Date").orElse("");

// 获取所有头
response.headers().map().forEach((name, values) -> {
    System.out.println(name + ": " + values);
});

处理表单数据

java
// application/x-www-form-urlencoded
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/login"))
    .header("Content-Type", "application/x-www-form-urlencoded")
    .POST(HttpRequest.BodyPublishers.ofString(
        "username=alice&password=secret"
    ))
    .build();

设置代理

java
HttpClient client = HttpClient.newBuilder()
    .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
    .build();
java
HttpClient client = HttpClient.newBuilder()
    .cookieHandler(new CookieManager())
    .build();

// 第一次请求(获取 Cookie)
HttpResponse<String> loginResponse = client.send(loginRequest,
    HttpResponse.BodyHandlers.ofString());

// 后续请求(自动带上 Cookie)
HttpResponse<String> dataResponse = client.send(dataRequest,
    HttpResponse.BodyHandlers.ofString());

错误处理

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

    if (response.statusCode() >= 200 && response.statusCode() < 300) {
        // 成功
        System.out.println(response.body());
    } else if (response.statusCode() == 404) {
        // 资源不存在
        System.out.println("Not Found");
    } else {
        // 其他错误
        System.err.println("Error: " + response.statusCode());
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.err.println("请求被中断");
} catch (ExecutionException e) {
    System.err.println("请求执行失败: " + e.getCause());
} catch (IOException e) {
    System.err.println("网络错误: " + e.getMessage());
}

小结

HTTP Client API 让 Java 原生 HTTP 请求变得简单:

特性说明
HTTP/1.1 + HTTP/2默认 HTTP/2,自动协商
同步/异步send()sendAsync()
连接池自动复用连接
WebSocketJDK 11+ 支持
内置无需第三方依赖
java
// 最简用法
HttpResponse<String> response = HttpClient.newHttpClient()
    .send(
        HttpRequest.newBuilder().uri(URI.create(url)).GET().build(),
        HttpResponse.BodyHandlers.ofString()
    );

从 JDK 11 开始,告别 HttpURLConnection 吧。

基于 VitePress 构建