首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java HttpClient 發送 POST 請求完整指南:同步/非同步、JSON、表單、檔案上傳、超時重試

     編輯:Java 相關知識

前言

JDK 11 將 java.net.http.HttpClient 納入標準庫,支援 HTTP/2、WebSocket、非同步、反應式串流,取代舊版 HttpURLConnection 與第三方 Apache HttpClient。本文系統整理 POST 請求的各種實戰場景:JSON API、表單提交、多部分檔案上傳、超時與重試策略、攔截器/日誌。

核心 HttpClient 建構

HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)          // 優先 HTTP/2
    .followRedirects(HttpClient.Redirect.NORMAL)
    .connectTimeout(Duration.ofSeconds(5))       // 連線超時
    .build();

場景一:發送 JSON (REST API 最常見)

String json = """
    {"userId": 1001, "action": "purchase", "amount": 99.9}
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/v1/order"))
    .header("Content-Type", "application/json")
    .header("Accept", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
    .timeout(Duration.ofSeconds(10)) // 請求超時
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());

場景二:表單提交 (application/x-www-form-urlencoded)

String form = "username=john&password=secret&rememberMe=true";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://auth.example.com/login"))
    .header("Content-Type", "application/x-www-form-urlencoded")
    .POST(HttpRequest.BodyPublishers.ofString(form, StandardCharsets.UTF_8))
    .build();

場景三:多部分檔案上傳 (multipart/form-data)

Path filePath = Paths.get("report.pdf");
String boundary = "Boundary-" + UUID.randomUUID();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (var out = new DataOutputStream(bos)) {
    // 文字欄位
    writePart(out, boundary, "userId", "1001", null);
    // 檔案欄位
    writeFilePart(out, boundary, "file", "report.pdf", "application/pdf", Files.readAllBytes(filePath));
    // 結束邊界
    out.writeBytes("--" + boundary + "--\r\n");
}

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/upload"))
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(HttpRequest.BodyPublishers.ofByteArray(bos.toByteArray()))
    .build();

private static void writePart(DataOutputStream out, String boundary, String name, String value, String contentType) throws IOException {
    out.writeBytes("--" + boundary + "\r\n");
    out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"\r\n");
    if (contentType != null) out.writeBytes("Content-Type: " + contentType + "\r\n");
    out.writeBytes("\r\n");
    out.writeBytes(value + "\r\n");
}
private static void writeFilePart(DataOutputStream out, String boundary, String name, String filename, String mime, byte[] content) throws IOException {
    out.writeBytes("--" + boundary + "\r\n");
    out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + filename + "\"\r\n");
    out.writeBytes("Content-Type: " + mime + "\r\n");
    out.writeBytes("\r\n");
    out.write(content);
    out.writeBytes("\r\n");
}

提示:生產環境建議使用 OkHttp 或 Apache HttpClient 5 的 MultipartEntityBuilder,避免手工拼裝錯誤。

場景四:非同步呼叫 (CompletableFuture)

CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenAccept(resp -> {
    System.out.println("Async status: " + resp.statusCode());
}).exceptionally(ex -> {
    ex.printStackTrace();
    return null;
});
// 主執行緒可繼續做其他事

場景五:串流大檔案下載/上傳 (零拷貝)

// 下載直接寫入檔案
HttpResponse<Path> resp = client.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("big.zip")));

// 上傳大檔案 (BodyPublisher.fromFile JDK 17+)
HttpRequest put = HttpRequest.newBuilder()
    .uri(URI.create("https://storage.example.com/bucket/big.zip"))
    .header("Content-Type", "application/octet-stream")
    .PUT(HttpRequest.BodyPublishers.ofFile(Paths.get("big.zip")))
    .build();

超時與重試策略 (Resilience4j 整合)

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-retry</artifactId>
    <version>2.1.0</version>
</dependency>

RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(500))
    .retryExceptions(IOException.class, HttpTimeoutException.class)
    .build();
RetryRegistry registry = RetryRegistry.of(config);
Retry retry = registry.retry("httpClient");

Supplier<HttpResponse<String>> supplier = Retry.decorateSupplier(retry, () ->
    client.send(request, HttpResponse.BodyHandlers.ofString()));
HttpResponse<String> resp = supplier.get();

攔截器/日誌 (自定義 HttpClient.Builder)

JDK HttpClient 無內建攔截器,若需統一日誌、簽名、Token 刷新,建議封裝:

public final class HttpClients {
    private static final HttpClient CLIENT = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(5))
        .build();

    public static HttpResponse<String> postJson(String url, String json) throws IOException, InterruptedException {
        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .timeout(Duration.ofSeconds(15))
            .build();
        long start = System.currentTimeMillis();
        HttpResponse<String> resp = CLIENT.send(req, HttpResponse.BodyHandlers.ofString());
        logRequest(url, json, resp, start);
        return resp;
    }

    private static void logRequest(String url, String reqBody, HttpResponse<String> resp, long start) {
        long cost = System.currentTimeMillis() - start;
        log.info("POST {} -> {} ({} ms) req={} resp={}",
                url, resp.statusCode(), cost, mask(reqBody), mask(resp.body()));
    }
    private static String mask(String s) { return s.length() > 500 ? s.substring(0, 500) + "..." : s; }
}

舊版 Apache HttpClient 遷移對照

Apache HttpClientJDK HttpClient
HttpClientBuilder.create().build()HttpClient.newBuilder().build()
HttpPostHttpRequest.newBuilder().POST(...)
UrlEncodedFormEntityBodyPublishers.ofString(form)
MultipartEntityBuilder手工拼裝或用 OkHttp
CloseableHttpResponseHttpResponse
EntityUtils.toString()response.body()
RequestConfig.setConnectTimeoutHttpClient.Builder.connectTimeout / HttpRequest.Builder.timeout
RetryHandlerResilience4j / 手工重試

最佳實踐清單

  1. 全域單一 HttpClient 實例 (執行緒安全、連線池複用)。
  2. 預設啟用 HTTP/2,降低延遲。
  3. 所有請求設定連線/讀取超時,避免卡死。
  4. JSON 序列化統一用 Jackson/Gson,勿手工拼字串。
  5. 敏感資料 (Token、密碼) 日誌遮罩。
  6. 大檔案上傳/下載使用 BodyPublishers.ofFile / BodyHandlers.ofFile 零拷貝。
  7. 非同步場景用 sendAsync + CompletableFuture,避免阻塞業務執行緒。
  8. 重試、熔斷、限流整合 Resilience4j 或 Sentinel。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved