|
|
@@ -0,0 +1,145 @@
|
|
|
+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());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|