Backend

99 Ngày Java — Ngày 98: HTTP Client

SSite Admin
2 tháng 11, 2026 3 phút đọc 6 lượt xem
99 Ngày Java — Ngày 98: HTTP Client

Trước Java 11, gọi HTTP bằng JDK nghĩa là vật lộn với HttpURLConnection, nên ai cũng thêm thư viện ngoài. Gói java.net.http mang tới một client hiện đại: API dạng builder, hỗ trợ HTTP/2, gọi đồng bộ lẫn bất đồng bộ. Với phần lớn nhu cầu gọi REST API, bạn không cần dependency nào khác ngoài một thư viện JSON.

HTTP Client

1. Ba đối tượng: client, request, response

HttpClient giữ cấu hình chung và pool kết nối; tạo một lần và dùng lại cho cả ứng dụng. HttpRequest là bất biến, mô tả một lời gọi: URI, phương thức, header, body. HttpResponse chứa mã trạng thái, header và body đã được chuyển đổi bởi một BodyHandler. Client tự thương lượng HTTP/2 và lùi về HTTP/1.1 khi server không hỗ trợ.

HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(2))
        .followRedirects(HttpClient.Redirect.NORMAL)
        .build();

HttpRequest request = HttpRequest.newBuilder(
            URI.create("https://api.example.com/orders/42"))
        .timeout(Duration.ofSeconds(3))
        .header("Accept", "application/json")
        .GET()
        .build();

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

2. Mã trạng thái không phải ngoại lệ

send() chỉ ném ngoại lệ khi không nhận được response: lỗi mạng là IOException, quá thời hạn là HttpTimeoutException. Response 404 hay 500 vẫn là một response hợp lệ, nên bạn phải tự kiểm tra statusCode(). Luôn đặt cả hai timeout: connectTimeout trên client cho việc mở kết nối, timeout trên request cho toàn bộ lời gọi. Không có timeout, một server treo sẽ giữ luồng của bạn vô thời hạn.

Order fetch(String id) throws IOException, InterruptedException {
    var response = client.send(requestFor(id),
            HttpResponse.BodyHandlers.ofString());
    return switch (response.statusCode()) {
        case 200 -> mapper.readValue(response.body(), Order.class);
        case 404 -> throw new OrderNotFoundException(id);
        default -> throw new UpstreamException(
                "Unexpected status " + response.statusCode());
    };
}

3. POST JSON và gọi bất đồng bộ

Body gửi đi được tạo bằng BodyPublishers; với JSON, serialize bằng Jackson rồi dùng ofString và đặt header Content-Type. sendAsync trả về CompletableFuture, cho phép gọi nhiều API song song rồi ghép kết quả. Trên Java 21, lời gọi send đồng bộ chạy trên virtual thread cũng cho hiệu quả tương tự mà code dễ đọc hơn, nên hãy chọn kiểu phù hợp với phần code xung quanh.

var body = mapper.writeValueAsString(new CreateOrder("sku-1", 2));
var post = HttpRequest.newBuilder(URI.create("https://api.example.com/orders"))
        .timeout(Duration.ofSeconds(3))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build();

CompletableFuture<Integer> status = client
        .sendAsync(post, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::statusCode);

4. Gọi API thật một cách có trách nhiệm

Một lời gọi ra ngoài có thể chậm, lỗi hoặc trả dữ liệu không như tài liệu. Chỉ thử lại với lỗi tạm thời như 503 hoặc timeout, có giãn cách tăng dần, và chỉ với thao tác idempotent; POST tạo đơn mà thử lại mù quáng sẽ tạo đơn trùng. Đọc header Retry-After khi nhận 429. Bọc client sau một interface của bạn, như adapter ở ngày 95, để logic nghiệp vụ không phụ thuộc vào chi tiết HTTP và test được bằng đối tượng giả.

  • Dùng lại một HttpClient; tạo mới cho mỗi request là bỏ phí pool kết nối.

  • Không ghi token hay toàn bộ body nhạy cảm ra log.

  • Response lớn thì dùng BodyHandlers.ofInputStream hoặc ofFile thay vì ofString.

  • Token đặt trong header Authorization, lấy từ cấu hình, không viết cứng trong code.

HTTP client của JDK đủ dùng cho phần lớn việc gọi API; phần khó là timeout, thử lại và xử lý lỗi. Ngày 99 khép lại series: nhìn lại chặng đường và chọn bước tiếp theo.

Tài liệu đối chiếu

  • https://docs.oracle.com/en/java/javase/21/docs/api/java.net.http/java/net/http/HttpClient.html

  • https://openjdk.org/groups/net/httpclient/intro.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 98: Best Practices Tổng hợp

Cấu trúc project chuẩn, những lỗi Spring hay gặp nhất và một checklist review dùng được ngay cho pull request.

2 thg 11, 20263 phút5
99 Ngày Spring — Ngày 97: Security Hardening cho Production

Security header, không lộ thông tin qua lỗi và Actuator, quét dependency và nguyên tắc quyền tối thiểu từ database tới container.

1 thg 11, 20263 phút0
99 Ngày Java — Ngày 97: JDBC

Connection và PreparedStatement, chống SQL injection, transaction thủ công và vì sao luôn cần connection pool.

1 thg 11, 20263 phút0