Java HttpClient: ตั้ง request timeout ไม่ให้ค้างรอปลายทาง
ผมเคยเขียน HTTP เรียกแล้วเธรดค้างเพราะปลายทางไม่ตอบ จนรู้ว่า HttpClient แยก timeout ของ connect กับของแต่ละคำขอได้ครับ
เอกสาร HttpClient และ HttpRequest.Builder#timeout อธิบายว่าตั้ง Duration ต่อคำขอได้ เมื่อหมดเวลาก่อนได้ผลลัพธ์ จะได้ HttpTimeoutException
ทำไมควรตั้งต่อคำขอ
timeout ทั้ง client ช่วยตอนเริ่มต่อ แต่คำขอที่อ่าน body นานยังค้างได้ การใส่ timeout ใน HttpRequest ทำให้แต่ละเส้นทางมีเพดานชัด เช่น health check สั้นกว่าดาวน์โหลดรายงาน
ตัวอย่างด้านล่างยืนยันว่า timeout ถูกแนบกับคำขอจริง และเมื่อส่งไปยังพอร์ตที่ไม่มีบริการ เครื่องจะล้มเร็วโดยไม่ค้างยาว
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.time.Duration;
public class Main {
public static void main(String[] args) throws Exception {
Duration requestTimeout = Duration.ofMillis(250);
HttpRequest request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:1/health"))
.timeout(requestTimeout)
.GET()
.build();
assert request.timeout().isPresent();
assert request.timeout().get().equals(requestTimeout);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(200))
.build();
long started = System.nanoTime();
boolean failedFast = false;
try {
client.send(request, HttpResponse.BodyHandlers.discarding());
} catch (IOException expected) {
failedFast = true;
}
long elapsedMs = Duration.ofNanos(System.nanoTime() - started).toMillis();
assert failedFast : "expected connection failure against a closed local port";
assert elapsedMs < 3000 : "client stayed blocked too long: " + elapsedMs + "ms";
System.out.println("ok");
}
}
ถ้าต้องการยกเลิกกลางคันเพิ่มเติม ใช้ sendAsync คู่กับ CompletableFuture และยกเลิกเอง แต่ขั้นแรกที่ขาดบ่อยคือใส่ timeout ใน request ให้ครบทุก call site ที่รอผล
Recap
- ตั้ง
HttpRequesttimeout แยกจาก connect timeout - ตรวจในเทสว่า timeout ถูกแนบกับคำขอ และความล้มเหลวเร็วไม่ค้าง
- จับ
HttpTimeoutExceptionในเส้นทางที่ปลายทางช้าจริง - ไล่ call site ที่ยังไม่มี timeout ก่อนปล่อยโปรดักชัน