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

Java 原生 HttpURLConnection 發送 POST 請求完整指南:從參數編碼到連線池優化

     編輯:Java 相關知識

前言

HttpURLConnection 是 JDK 內建的 HTTP 客戶端,零依賴、輕量級、Android 相容。雖然功能不如 Apache HttpClient 豐富,但在簡單場景、受限環境、避免第三方依賴時仍是首選。本文系統性介紹 POST 請求完整流程、參數編碼、超時控制、重導向處理與效能優化。

核心類別關係

URL
  │
  ▼
URLConnection (抽象類)
  │
  ▼
HttpURLConnection ← 我們實際使用的類
  │
  ├── getOutputStream() → 寫入請求體
  ├── getInputStream()  ← 讀取回應體
  ├── getErrorStream()  ← 讀取錯誤回應體 (4xx/5xx)
  └── disconnect()      ← 釋放連線

標準 POST 流程完整代碼

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class HttpPostClient {

    public static String post(String urlStr, Map params, int connectTimeout, int readTimeout) 
            throws IOException {
        
        // 1. 建立 URL 與連線
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        
        try {
            // 2. 核心配置
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);        // 允許寫入請求體
            conn.setDoInput(true);         // 允許讀取回應體
            conn.setUseCaches(false);      // 不使用快取
            conn.setConnectTimeout(connectTimeout);
            conn.setReadTimeout(readTimeout);
            conn.setInstanceFollowRedirects(true); // 自動跟隨重導向
            
            // 3. 設定請求 Header
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            conn.setRequestProperty("Accept", "application/json");
            conn.setRequestProperty("User-Agent", "Java/HttpURLConnection");
            conn.setRequestProperty("Connection", "Keep-Alive"); // 長連線
            
            // 4. 寫入請求體 (表單參數)
            String formData = buildFormData(params);
            try (OutputStream os = conn.getOutputStream();
                 OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
                 BufferedWriter writer = new BufferedWriter(osw)) {
                writer.write(formData);
                writer.flush();
            }
            
            // 5. 處理回應
            int status = conn.getResponseCode();
            InputStream is = (status >= 200 && status < 300) ? conn.getInputStream() : conn.getErrorStream();
            
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                return sb.toString();
            }
        } finally {
            conn.disconnect(); // 釋放連線回連線池
        }
    }

    /** 建立 application/x-www-form-urlencoded 字串 */
    private static String buildFormData(Map params) {
        if (params == null || params.isEmpty()) return "";
        StringBuilder sb = new StringBuilder();
        for (Map.Entry entry : params.entrySet()) {
            if (sb.length() > 0) sb.append('&');
            sb.append(encode(entry.getKey())).append('=').append(encode(entry.getValue()));
        }
        return sb.toString();
    }

    /** URL 編碼 (RFC 3986) */
    private static String encode(String s) {
        try {
            return URLEncoder.encode(s, StandardCharsets.UTF_8.name())
                    .replace("+", "%20")  // 標準編碼空格為 %20
                    .replace("*", "%2A")
                    .replace("~", "%7E");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e); // 不會發生
        }
    }

    // 測試主程式
    public static void main(String[] args) {
        try {
            Map params = Map.of(
                "username", "test_user",
                "password", "123456",
                "remember", "true"
            );
            String result = post("https://localhost:8080/api/login", params, 5000, 10000);
            System.out.println("回應: " + result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

關鍵技術細節解析

1. 請求體寫入機制

  • setDoOutput(true) 必須在 getOutputStream() 前呼叫
  • 預設 Content-Type 為 application/x-www-form-urlencoded
  • 輸出流關閉前資料不會真正發送 (緩衝機制)

2. 超時設定雙重保險

⚠️ 必須兩者都設,否則預設為無限等待,導致線程阻塞耗盡。

3. 重導向處理

  • setInstanceFollowRedirects(true):單一連線層級啟用
  • HttpURLConnection.setFollowRedirects(true):全域靜態啟用 (影響所有連線)
  • POST 重導向預設行為:HTTP 1.1 規範要求 301/302 重導向時保持 POST 方法,但瀏覽器常改為 GET,Java 預設遵循規範保持 POST

4. 連線複用與 Keep-Alive

// 啟用 Keep-Alive (HTTP/1.1 預設開啟)
conn.setRequestProperty("Connection", "Keep-Alive");

// JDK 內建連線池 (透過 sun.net.www.http.HttpClient 控制)
// 系統屬性調整 (啟動時設定 -D 或 System.setProperty)
System.setProperty("http.keepAlive", "true");
System.setProperty("http.maxConnections", "50"); // 每個目的地最大連線數

進階場景:JSON POST / 檔案上傳

發送 JSON

conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
String json = "{\"name\":\"test\",\"age\":25}";
try (OutputStream os = conn.getOutputStream()) {
    os.write(json.getBytes(StandardCharsets.UTF_8));
}

Multipart 檔案上傳 (手動構建)

String boundary = "----Boundary" + System.currentTimeMillis();
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (OutputStream os = conn.getOutputStream();
     PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true)) {
    
    // 文字欄位
    writer.append("--").append(boundary).append("\r\n");
    writer.append("Content-Disposition: form-data; name=\"username\"\r\n\r\n");
    writer.append("test_user").append("\r\n");
    
    // 檔案欄位
    writer.append("--").append(boundary).append("\r\n");
    writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n");
    writer.append("Content-Type: text/plain\r\n\r\n");
    writer.flush();
    Files.copy(Paths.get("test.txt"), os);
    os.write("\r\n".getBytes());
    
    // 結束邊界
    writer.append("--").append(boundary).append("--\r\n");
    writer.flush();
}

效能優化與最佳實務

  1. 複用 URLConnection:同一 URL 物件可多次 openConnection(),底層共享連線池
  2. 關閉連線:務必在 finally 呼叫 disconnect(),否則連線洩漏
  3. 串流處理大回應:避免 EntityUtils.toString() 式一次性讀入,改用 BufferedReader 逐行或串流處理
  4. 錯誤流處理:4xx/5xx 狀態碼需讀取 getErrorStream() 獲取錯誤詳情
  5. 編碼一致性:請求/回應/URL 編碼統一使用 UTF-8

常見錯誤排查表

方法含義建議值
setConnectTimeout(ms)TCP 三向交握建立連線最長等待3000-5000ms
setReadTimeout(ms)從伺服器讀取資料間隔最長等待10000-30000ms

總結

HttpURLConnection 雖 API 較低階,但掌握其請求/回應流、超時控制、編碼處理、連線釋放核心機制,即可應對 80% 的 HTTP 整合場景。若需連線池監控、自動重試、攔截器、OAuth 等進階功能,再升級 Apache HttpClient 或 OkHttp。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
現象可能原因解決方案
連線超時防火牆、IP錯誤、埠未開放telnet 測試、檢查 security group
讀取超時後端處理過慢、死鎖調大 readTimeout、優化後端
400 Bad Request參數格式錯誤、編碼不符抓包對照、檢查 Content-Type
中文亂碼編碼不一致全鏈路 UTF-8、URLEncoder.encode
連線洩漏未呼叫 disconnect()finally 區塊強制釋放