package space.anyi.httpClient; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.time.Duration; /** * HTTP Request 配置项示例 * *

HttpRequest 本身可通过 Builder 配置的项(区别于 HttpClient 级配置):

* */ public class RequestConfigExample { /** 服务基地址常量 */ private static final String BASE_URL = "http://localhost:8080"; /** * 请求级超时:timeout 作用于「从发送到拿到完整响应体」的整个过程。 */ public HttpRequest buildWithTimeout() { return HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/api/users")) // 整个请求 3 秒内必须完成,否则抛 HttpTimeoutException .timeout(Duration.ofSeconds(3)) .GET() .build(); } /** * 请求级协议版本:覆盖 HttpClient 上的 version 配置。 */ public HttpRequest buildWithVersion() { return HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/api/users")) .version(HttpClient.Version.HTTP_1_1) .GET() .build(); } /** * 期望继续:expectContinue(true) 会附加 Expect: 100-continue 请求头。 * *

典型用途:提交大请求体前先「询价」,服务器返回 100 后再真正发送请求体, * 避免无谓地传输大文件。

*/ public HttpRequest buildWithExpectContinue() { return HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/api/users")) .header("Content-Type", "application/json") // 声明期望服务器先返回 100-continue .expectContinue(true) .POST(BodyPublishers.ofString("{\"name\":\"x\",\"account\":\"y\",\"sex\":\"z\"}")) .build(); } /** * 自定义请求方法:method(name, bodyPublisher)。 * *

GET/POST/PUT/DELETE 之外的任意方法(如 PATCH、HEAD)都用这个入口。 * 无请求体的方法传 BodyPublishers.noBody()。

*/ public HttpRequest buildWithCustomMethod() { return HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/api/users")) // 自定义 PATCH 方法,无请求体 .method("PATCH", BodyPublishers.noBody()) .build(); } /** * 复制 Builder:copy() 返回独立的副本,之后修改副本不影响原 Builder。 * *

JDK 的 copy() 采用「写时复制」:复制后两者的配置相互独立, * 后续对任一方的修改都不会影响另一方。

*/ public CopyPair buildWithCopy() { // 原 Builder:只带 X-Original 头 HttpRequest.Builder original = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/api/users")) .header("X-Original", "yes"); // 复制并修改副本:加上 X-Copy 头(使用不同 header 名便于验证独立性) HttpRequest copied = original.copy() .header("X-Copy", "yes") .GET() .build(); // 修改副本后,原 Builder 依旧只含 X-Original HttpRequest originalBuilt = original.GET().build(); return new CopyPair(originalBuilt, copied); } /** * 携带「复制前的原请求」与「复制后修改的副本请求」的成对结果。 */ public record CopyPair(HttpRequest original, HttpRequest copied) { } /** * 使用带超时的请求发送 GET,验证请求级配置不影响请求执行。 */ public HttpResponse sendWithTimeout() throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); return client.send(buildWithTimeout(), BodyHandlers.ofString()); } /** * 演示请求级超时生效:请求体较大的场景下服务器迟迟不应答时应快速失败。 * 这里用一个不可达地址 + 短超时演示。 */ public void demoTimeoutExpired() { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:9999")) // 极短超时,验证超时机制 .timeout(Duration.ofSeconds(1)) .GET() .build(); try { client.send(request, BodyHandlers.ofString()); } catch (java.net.http.HttpTimeoutException e) { System.out.println("请求超时: " + e.getMessage()); } catch (IOException | InterruptedException e) { System.out.println("请求异常: " + e.getClass().getSimpleName()); } } }