| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- 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 配置项示例
- *
- * <p>HttpRequest 本身可通过 Builder 配置的项(区别于 HttpClient 级配置):</p>
- * <ul>
- * <li>{@code .uri(URI)}:请求地址(必填)</li>
- * <li>{@code .timeout(Duration)}:<b>整个请求</b>的超时时间(拿完响应体)
- * ,与 client 的 connectTimeout(仅连接阶段)不同</li>
- * <li>{@code .expectContinue(true)}:发送 Expect: 100-continue 头,
- * 请求体较大时先探询服务器是否愿意接收</li>
- * <li>{@code .version(HTTP_1_1/HTTP_2)}:请求级协议版本(覆盖 client 配置)</li>
- * <li>{@code .GET()/.POST(pub)/.PUT(pub)/.DELETE()/.method(name,pub)}:请求方法</li>
- * <li>{@code .copy()}:复制 Builder,修改副本不影响原 Builder</li>
- * </ul>
- */
- 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 请求头。
- *
- * <p>典型用途:提交大请求体前先「询价」,服务器返回 100 后再真正发送请求体,
- * 避免无谓地传输大文件。</p>
- */
- 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)。
- *
- * <p>GET/POST/PUT/DELETE 之外的任意方法(如 PATCH、HEAD)都用这个入口。
- * 无请求体的方法传 BodyPublishers.noBody()。</p>
- */
- public HttpRequest buildWithCustomMethod() {
- return HttpRequest.newBuilder()
- .uri(URI.create(BASE_URL + "/api/users"))
- // 自定义 PATCH 方法,无请求体
- .method("PATCH", BodyPublishers.noBody())
- .build();
- }
- /**
- * 复制 Builder:copy() 返回独立的副本,之后修改副本不影响原 Builder。
- *
- * <p>JDK 的 copy() 采用「写时复制」:复制后两者的配置相互独立,
- * 后续对任一方的修改都不会影响另一方。</p>
- */
- 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<String> 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());
- }
- }
- }
|