RequestConfigExample.java 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package space.anyi.httpClient;
  2. import java.io.IOException;
  3. import java.net.URI;
  4. import java.net.http.HttpClient;
  5. import java.net.http.HttpRequest;
  6. import java.net.http.HttpRequest.BodyPublishers;
  7. import java.net.http.HttpResponse;
  8. import java.net.http.HttpResponse.BodyHandlers;
  9. import java.time.Duration;
  10. /**
  11. * HTTP Request 配置项示例
  12. *
  13. * <p>HttpRequest 本身可通过 Builder 配置的项(区别于 HttpClient 级配置):</p>
  14. * <ul>
  15. * <li>{@code .uri(URI)}:请求地址(必填)</li>
  16. * <li>{@code .timeout(Duration)}:<b>整个请求</b>的超时时间(拿完响应体)
  17. * ,与 client 的 connectTimeout(仅连接阶段)不同</li>
  18. * <li>{@code .expectContinue(true)}:发送 Expect: 100-continue 头,
  19. * 请求体较大时先探询服务器是否愿意接收</li>
  20. * <li>{@code .version(HTTP_1_1/HTTP_2)}:请求级协议版本(覆盖 client 配置)</li>
  21. * <li>{@code .GET()/.POST(pub)/.PUT(pub)/.DELETE()/.method(name,pub)}:请求方法</li>
  22. * <li>{@code .copy()}:复制 Builder,修改副本不影响原 Builder</li>
  23. * </ul>
  24. */
  25. public class RequestConfigExample {
  26. /** 服务基地址常量 */
  27. private static final String BASE_URL = "http://localhost:8080";
  28. /**
  29. * 请求级超时:timeout 作用于「从发送到拿到完整响应体」的整个过程。
  30. */
  31. public HttpRequest buildWithTimeout() {
  32. return HttpRequest.newBuilder()
  33. .uri(URI.create(BASE_URL + "/api/users"))
  34. // 整个请求 3 秒内必须完成,否则抛 HttpTimeoutException
  35. .timeout(Duration.ofSeconds(3))
  36. .GET()
  37. .build();
  38. }
  39. /**
  40. * 请求级协议版本:覆盖 HttpClient 上的 version 配置。
  41. */
  42. public HttpRequest buildWithVersion() {
  43. return HttpRequest.newBuilder()
  44. .uri(URI.create(BASE_URL + "/api/users"))
  45. .version(HttpClient.Version.HTTP_1_1)
  46. .GET()
  47. .build();
  48. }
  49. /**
  50. * 期望继续:expectContinue(true) 会附加 Expect: 100-continue 请求头。
  51. *
  52. * <p>典型用途:提交大请求体前先「询价」,服务器返回 100 后再真正发送请求体,
  53. * 避免无谓地传输大文件。</p>
  54. */
  55. public HttpRequest buildWithExpectContinue() {
  56. return HttpRequest.newBuilder()
  57. .uri(URI.create(BASE_URL + "/api/users"))
  58. .header("Content-Type", "application/json")
  59. // 声明期望服务器先返回 100-continue
  60. .expectContinue(true)
  61. .POST(BodyPublishers.ofString("{\"name\":\"x\",\"account\":\"y\",\"sex\":\"z\"}"))
  62. .build();
  63. }
  64. /**
  65. * 自定义请求方法:method(name, bodyPublisher)。
  66. *
  67. * <p>GET/POST/PUT/DELETE 之外的任意方法(如 PATCH、HEAD)都用这个入口。
  68. * 无请求体的方法传 BodyPublishers.noBody()。</p>
  69. */
  70. public HttpRequest buildWithCustomMethod() {
  71. return HttpRequest.newBuilder()
  72. .uri(URI.create(BASE_URL + "/api/users"))
  73. // 自定义 PATCH 方法,无请求体
  74. .method("PATCH", BodyPublishers.noBody())
  75. .build();
  76. }
  77. /**
  78. * 复制 Builder:copy() 返回独立的副本,之后修改副本不影响原 Builder。
  79. *
  80. * <p>JDK 的 copy() 采用「写时复制」:复制后两者的配置相互独立,
  81. * 后续对任一方的修改都不会影响另一方。</p>
  82. */
  83. public CopyPair buildWithCopy() {
  84. // 原 Builder:只带 X-Original 头
  85. HttpRequest.Builder original = HttpRequest.newBuilder()
  86. .uri(URI.create(BASE_URL + "/api/users"))
  87. .header("X-Original", "yes");
  88. // 复制并修改副本:加上 X-Copy 头(使用不同 header 名便于验证独立性)
  89. HttpRequest copied = original.copy()
  90. .header("X-Copy", "yes")
  91. .GET()
  92. .build();
  93. // 修改副本后,原 Builder 依旧只含 X-Original
  94. HttpRequest originalBuilt = original.GET().build();
  95. return new CopyPair(originalBuilt, copied);
  96. }
  97. /**
  98. * 携带「复制前的原请求」与「复制后修改的副本请求」的成对结果。
  99. */
  100. public record CopyPair(HttpRequest original, HttpRequest copied) {
  101. }
  102. /**
  103. * 使用带超时的请求发送 GET,验证请求级配置不影响请求执行。
  104. */
  105. public HttpResponse<String> sendWithTimeout() throws IOException, InterruptedException {
  106. HttpClient client = HttpClient.newHttpClient();
  107. return client.send(buildWithTimeout(), BodyHandlers.ofString());
  108. }
  109. /**
  110. * 演示请求级超时生效:请求体较大的场景下服务器迟迟不应答时应快速失败。
  111. * 这里用一个不可达地址 + 短超时演示。
  112. */
  113. public void demoTimeoutExpired() {
  114. HttpClient client = HttpClient.newHttpClient();
  115. HttpRequest request = HttpRequest.newBuilder()
  116. .uri(URI.create("http://localhost:9999"))
  117. // 极短超时,验证超时机制
  118. .timeout(Duration.ofSeconds(1))
  119. .GET()
  120. .build();
  121. try {
  122. client.send(request, BodyHandlers.ofString());
  123. } catch (java.net.http.HttpTimeoutException e) {
  124. System.out.println("请求超时: " + e.getMessage());
  125. } catch (IOException | InterruptedException e) {
  126. System.out.println("请求异常: " + e.getClass().getSimpleName());
  127. }
  128. }
  129. }