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.

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


