|
|
@@ -0,0 +1,164 @@
|
|
|
+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.HttpResponse;
|
|
|
+import java.net.http.HttpResponse.BodyHandler;
|
|
|
+import java.net.http.HttpResponse.BodyHandlers;
|
|
|
+import java.net.http.HttpResponse.BodySubscriber;
|
|
|
+import java.net.http.HttpResponse.BodySubscribers;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 响应处理器示例:BodyHandler 决定「如何消费响应体」
|
|
|
+ *
|
|
|
+ * <p>BodyHandler 在拿到响应头(状态码等)后被调用,返回一个 BodySubscriber,
|
|
|
+ * 后者负责把响应体字节流转换为目标类型 T。发送请求时通过第二个参数传入:
|
|
|
+ * {@code client.send(request, myBodyHandler)}。</p>
|
|
|
+ *
|
|
|
+ * <p>本章节演示两部分:</p>
|
|
|
+ * <ul>
|
|
|
+ * <li><b>JDK 内置的 BodyHandlers</b>:ofString / ofByteArray / ofFile /
|
|
|
+ * ofInputStream / discarding</li>
|
|
|
+ * <li><b>自定义 BodyHandler</b>:按状态码分流响应体,或对响应做后处理</li>
|
|
|
+ * </ul>
|
|
|
+ */
|
|
|
+public class ResponseHandlerExample {
|
|
|
+
|
|
|
+ /** 服务基地址常量 */
|
|
|
+ private static final String BASE_URL = "http://localhost:8080";
|
|
|
+
|
|
|
+ private final HttpClient httpClient = HttpClient.newHttpClient();
|
|
|
+
|
|
|
+ /** 构造 GET /api/users 的请求 */
|
|
|
+ private HttpRequest buildListRequest() {
|
|
|
+ return HttpRequest.newBuilder()
|
|
|
+ .uri(URI.create(BASE_URL + "/api/users"))
|
|
|
+ .GET()
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内置响应处理器一:ofString — 响应体转为字符串(最常用)。
|
|
|
+ */
|
|
|
+ public HttpResponse<String> getAsString() throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), BodyHandlers.ofString());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内置响应处理器二:ofByteArray — 响应体转为字节数组,
|
|
|
+ * 适合二进制内容(图片、文件等)。
|
|
|
+ */
|
|
|
+ public HttpResponse<byte[]> getAsByteArray() throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), BodyHandlers.ofByteArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内置响应处理器三:ofFile(Path) — 直接把响应体写入磁盘文件,
|
|
|
+ * 适合下载大文件,无需先把内容加载进内存。
|
|
|
+ *
|
|
|
+ * @param target 目标保存路径
|
|
|
+ * @return 响应对象,其 body 为文件路径
|
|
|
+ */
|
|
|
+ public HttpResponse<Path> getAsFile(Path target) throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), BodyHandlers.ofFile(target));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内置响应处理器四:ofInputStream — 以输入流接收响应体,
|
|
|
+ * 适合边下载边处理。
|
|
|
+ */
|
|
|
+ public HttpResponse<java.io.InputStream> getAsInputStream()
|
|
|
+ throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), BodyHandlers.ofInputStream());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内置响应处理器五:discarding — 丢弃响应体,body() 返回 null。
|
|
|
+ * 适合只关心状态码、不关心内容的场景(如健康检查)。
|
|
|
+ */
|
|
|
+ public HttpResponse<Void> getDiscarded() throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), BodyHandlers.discarding());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义响应处理器一:按状态码分流。
|
|
|
+ *
|
|
|
+ * <p>2xx 时正常读取响应体;非 2xx(如 404)时丢弃响应体,返回 null。
|
|
|
+ * 通过 {@link BodySubscribers#replacing(Object)} 把订阅器替换为固定值。</p>
|
|
|
+ */
|
|
|
+ public BodyHandler<String> statusBasedHandler() {
|
|
|
+ // apply 方法返回 BodySubscriber,参数 ResponseInfo 携带状态码与响应头
|
|
|
+ return responseInfo -> {
|
|
|
+ if (responseInfo.statusCode() >= 200 && responseInfo.statusCode() < 300) {
|
|
|
+ // 成功:正常读取响应体为字符串
|
|
|
+ return BodySubscribers.ofString(StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+ // 失败:丢弃内容,最终 body() 返回 null
|
|
|
+ return BodySubscribers.replacing(null);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自定义响应处理器二:对响应体做后处理。
|
|
|
+ *
|
|
|
+ * <p>用 {@link BodySubscribers#mapping(BodySubscriber, Function)} 把
|
|
|
+ * 上游订阅器转换后的结果再处理一次,此处给响应体加上一个前缀标记。</p>
|
|
|
+ */
|
|
|
+ public BodyHandler<String> mappingHandler(String tag) {
|
|
|
+ BodySubscriber<String> upstream = BodySubscribers.ofString(StandardCharsets.UTF_8);
|
|
|
+ // 上游读到完整字符串后,再拼接业务标记前缀
|
|
|
+ return responseInfo ->
|
|
|
+ BodySubscribers.mapping(upstream, body -> "[" + tag + "] " + body);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 演示使用自定义处理器发送请求。
|
|
|
+ *
|
|
|
+ * @param handler 自定义响应处理器
|
|
|
+ * @return 处理后的响应
|
|
|
+ */
|
|
|
+ public HttpResponse<String> sendWith(BodyHandler<String> handler)
|
|
|
+ throws IOException, InterruptedException {
|
|
|
+ return httpClient.send(buildListRequest(), handler);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 演示:查询不存在的用户(404)时使用按状态码分流的自定义处理器,
|
|
|
+ * 此时 body() 应为 null。
|
|
|
+ */
|
|
|
+ public HttpResponse<String> getNotFoundWithCustomHandler()
|
|
|
+ throws IOException, InterruptedException {
|
|
|
+ HttpRequest request = HttpRequest.newBuilder()
|
|
|
+ .uri(URI.create(BASE_URL + "/api/users/999999999"))
|
|
|
+ .GET()
|
|
|
+ .build();
|
|
|
+
|
|
|
+ return httpClient.send(request, statusBasedHandler());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主演示方法:依次展示各类响应处理器的输出。
|
|
|
+ */
|
|
|
+ public void demo() throws IOException, InterruptedException {
|
|
|
+ // ofString
|
|
|
+ System.out.println("ofString: " + abbreviate(getAsString().body()));
|
|
|
+ // ofByteArray
|
|
|
+ System.out.println("ofByteArray: " + getAsByteArray().body().length + " bytes");
|
|
|
+ // ofFile
|
|
|
+ Path file = Files.createTempFile("resp", ".json");
|
|
|
+ getAsFile(file);
|
|
|
+ System.out.println("ofFile 已写入: " + file);
|
|
|
+ // discarding
|
|
|
+ System.out.println("discarding body: " + getDiscarded().body());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 截断长文本 */
|
|
|
+ private String abbreviate(String s) {
|
|
|
+ return s.length() > 80 ? s.substring(0, 80) + "..." : s;
|
|
|
+ }
|
|
|
+}
|