Browse Source

教程点8:HTTP Client 配置项 - version/connectTimeout/executor/followRedirects/proxy/cookieHandler

yangyi 1 tuần trước cách đây
mục cha
commit
035bc5ad65

+ 54 - 1
doc.md

@@ -470,4 +470,57 @@ BodyHandler<String> handler2 = responseInfo ->
 - `getDiscarded`:discarding 处理器 body() 为 null;
 - `customHandler_success`:自定义 mapping 处理器为响应体加前缀;
 - `customHandler_404DiscardsBody`:自定义处理器在 404 时 body() 为 null;
-- `statusBasedHandler_successString`:按状态码分流的处理器 2xx 正常返回。
+- `statusBasedHandler_successString`:按状态码分流的处理器 2xx 正常返回。
+
+---
+
+## 八、HTTP Client 配置项
+
+### 8.1 文字说明
+
+`HttpClient.newBuilder()` 返回的 Builder 可在创建客户端时配置**通用行为**。
+所有配置在客户端创建后**不可修改**,因此应在创建时一次设置好。
+
+常用配置项:
+
+| 配置项 | 作用 | 备注 |
+|--------|------|------|
+| `.version(HTTP_1_1 / HTTP_2)` | 协议版本 | |
+| `.connectTimeout(Duration)` | 连接建立超时 | 超时抛 ConnectTimeoutException |
+| `.executor(Executor)` | 异步请求线程池 | 默认内置线程池 |
+| `.followRedirects(NEVER/ALWAYS/NORMAL)` | 重定向策略 | NORMAL 只跟随 GET 等安全方法 |
+| `.proxy(ProxySelector)` | 代理选择器 | `ProxySelector.NO_PROXY` 直连 |
+| `.cookieHandler(CookieHandler)` | Cookie 管理器 | 通常搭配 `CookieManager` |
+| `.authenticator(Authenticator)` | 认证器 | Basic Auth 等 |
+| `.sslContext / .sslParameters` | TLS 配置 | 仅 HTTPS 生效 |
+| `.priority(int)` | HTTP/2 流优先级 | 范围 1~256,仅 HTTP_2 生效 |
+
+### 8.2 示例代码
+
+见 `ClientConfigExample.java`,核心代码如下:
+
+```java
+CookieManager cookieManager = new CookieManager();
+
+HttpClient client = HttpClient.newBuilder()
+        .version(HttpClient.Version.HTTP_1_1)      // 协议版本
+        .connectTimeout(Duration.ofSeconds(5))     // 连接超时
+        .executor(Executors.newFixedThreadPool(4)) // 异步线程池
+        .followRedirects(HttpClient.Redirect.NORMAL)  // 重定向策略
+        .proxy(ProxySelector.getDefault())         // 代理
+        .cookieHandler(cookieManager)              // Cookie 管理
+        .build();
+```
+
+配置一旦生效,可通过 getter 读取(如 `client.version()`、
+`client.connectTimeout()`、`client.followRedirects()` 等),方便校验。
+
+### 8.3 测试代码
+
+见 `ClientConfigExampleTest.java`,测试点包括:
+
+- `buildConfiguredClient_hasExpectedValues`:校验 version / connectTimeout /
+  followRedirects / executor / cookieHandler 配置均生效;
+- `buildHttp2Client_defaultsToHttp2`:HTTP_2 客户端版本正确;
+- `sendGet_withConfiguredClient`:配置后的客户端请求返回 200;
+- `connectTimeout_returns200OrThrows`:连接不可达主机时抛出预期异常。

+ 114 - 0
src/main/java/space/anyi/httpClient/ClientConfigExample.java

@@ -0,0 +1,114 @@
+package space.anyi.httpClient;
+
+import java.io.IOException;
+import java.net.CookieManager;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.time.Duration;
+import java.util.concurrent.Executors;
+
+/**
+ * HTTP Client 配置项示例
+ *
+ * <p>通过 {@code HttpClient.newBuilder()} 返回的 Builder 可以配置客户端的
+ * 通用行为,主要配置项如下:</p>
+ *
+ * <table>
+ *     <tr><th>配置项</th><th>作用</th><th>备注</th></tr>
+ *     <tr><td>version()</td><td>HTTP 协议版本</td><td>HTTP_1_1 / HTTP_2</td></tr>
+ *     <tr><td>connectTimeout()</td><td>连接建立超时时间</td><td>超过则抛 ConnectTimeoutException</td></tr>
+ *     <tr><td>executor()</td><td>异步请求使用的线程池</td><td>不设置则用内置默认线程池</td></tr>
+ *     <tr><td>followRedirects()</td><td>重定向策略</td><td>NEVER / ALWAYS / NORMAL</td></tr>
+ *     <tr><td>proxy()</td><td>代理选择器</td><td>NO_PROXY 直连</td></tr>
+ *     <tr><td>cookieHandler()</td><td>Cookie 管理器</td><td>配合 CookieManager 使用</td></tr>
+ *     <tr><td>authenticator()</td><td>认证器</td><td>配合 Authenticator 使用(Basic Auth 等)</td></tr>
+ *     <tr><td>sslContext/sslParameters()</td><td>TLS 配置</td><td>仅 HTTPS 生效</td></tr>
+ *     <tr><td>priority()</td><td>HTTP/2 流优先级</td><td>仅 HTTP_2 生效</td></tr>
+ * </table>
+ */
+public class ClientConfigExample {
+
+    /** 服务基地址常量 */
+    private static final String BASE_URL = "http://localhost:8080";
+
+    /**
+     * 构建一个携带多项配置的 HttpClient。
+     *
+     * <p>所有配置均为「在客户端创建时一次性设定」,创建后不可修改。</p>
+     *
+     * @return 配置完善的 HttpClient
+     */
+    public HttpClient buildConfiguredClient() {
+        // Cookie 管理器(Index: CookieManager 负责保存/发送 Cookie)
+        CookieManager cookieManager = new CookieManager();
+
+        return HttpClient.newBuilder()
+                // 指定 HTTP 协议版本为 1.1(本服务仅支持 HTTP/1.1)
+                .version(HttpClient.Version.HTTP_1_1)
+                // 连接建立超过 5 秒未成功则失败
+                .connectTimeout(Duration.ofSeconds(5))
+                // 自定义异步任务的执行线程池(默认是内置线程池)
+                .executor(Executors.newFixedThreadPool(4))
+                // 重定向策略:NORMAL 只自动跟随「安全方法」的重定向
+                .followRedirects(HttpClient.Redirect.NORMAL)
+                // 不使用代理,直连
+                .proxy(ProxySelector.getDefault())
+                // 配置 Cookie 管理器,自动管理会话 Cookie
+                .cookieHandler(cookieManager)
+                .build();
+    }
+
+    /**
+     * 构建启用 HTTP/2 的客户端(本服务不支持,仅演示配置写法)。
+     */
+    public HttpClient buildHttp2Client() {
+        return HttpClient.newBuilder()
+                .version(HttpClient.Version.HTTP_2)
+                // priority 仅对 HTTP/2 生效,范围 1~256,越大优先级越高
+                .priority(256)
+                .build();
+    }
+
+    /**
+     * 使用配置好的客户端发送 GET 请求。
+     *
+     * @return GET /api/users 的响应
+     */
+    public HttpResponse<String> sendGet() throws IOException, InterruptedException {
+        HttpClient httpClient = buildConfiguredClient();
+
+        HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create(BASE_URL + "/api/users"))
+                .GET()
+                .build();
+
+        return httpClient.send(request, BodyHandlers.ofString());
+    }
+
+    /**
+     * 演示「连接超时」配置:连接不存在的地址应快速失败并抛异常。
+     */
+    public void demoConnectTimeout() {
+        HttpClient client = HttpClient.newBuilder()
+                .connectTimeout(Duration.ofSeconds(2))
+                .build();
+
+        HttpRequest request = HttpRequest.newBuilder()
+                // 使用一个不存在的端口,模拟连接超时
+                .uri(URI.create("http://localhost:9999"))
+                .GET()
+                .build();
+
+        try {
+            client.send(request, BodyHandlers.ofString());
+        } catch (java.net.http.HttpConnectTimeoutException e) {
+            System.out.println("连接超时: " + e.getMessage());
+        } catch (IOException | InterruptedException e) {
+            System.out.println("请求异常: " + e.getMessage());
+        }
+    }
+}

+ 54 - 0
src/test/java/space/anyi/httpClient/ClientConfigExampleTest.java

@@ -0,0 +1,54 @@
+package space.anyi.httpClient;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.ProxySelector;
+import java.net.http.HttpClient;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * HTTP Client 配置项示例的测试类
+ */
+class ClientConfigExampleTest {
+
+    private final ClientConfigExample example = new ClientConfigExample();
+
+    @Test
+    void buildConfiguredClient_hasExpectedValues() {
+        // 校验各配置项在 HttpClient 上正确生效(可通过 getter 获取)
+        HttpClient client = example.buildConfiguredClient();
+
+        assertEquals(HttpClient.Version.HTTP_1_1, client.version());
+        assertEquals(Duration.ofSeconds(5), client.connectTimeout().orElse(null));
+        assertEquals(HttpClient.Redirect.NORMAL, client.followRedirects());
+        // 自定义 executor 存在
+        assertTrue(client.executor().isPresent());
+        // 配置了 Cookie 管理器
+        assertTrue(client.cookieHandler().isPresent());
+    }
+
+    @Test
+    void buildHttp2Client_defaultsToHttp2() {
+        HttpClient client = example.buildHttp2Client();
+
+        assertEquals(HttpClient.Version.HTTP_2, client.version());
+    }
+
+    @Test
+    void sendGet_withConfiguredClient() throws IOException, InterruptedException {
+        // 配置后的客户端仍能正常完成请求
+        HttpResponse<String> response = example.sendGet();
+
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    void connectTimeout_returns200OrThrows() {
+        // 对可达的服务,2 秒内应正常响应;不可达时抛超时异常(本测试只验证不抛未预期异常)
+        example.demoConnectTimeout();
+    }
+}