雖然 HttpClient、OkHttp、WebClient 更便利,但 java.net.HttpURLConnection (底層 URLConnection) 是零依賴、JDK 內建的選擇,適合輕量級場景、受限環境或理解 HTTP 協議細節。本文提供生產級封裝類 HttpRequestor,支援 GET/POST、連線/讀取超時、HTTP/HTTPS 代理、字元編碼、資源安全釋放。
params) throws IOException {
String body = buildFormBody(params);
HttpURLConnection conn = createConnection(new URL(url), "POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + charset);
conn.setRequestProperty("Content-Length", Integer.toString(body.getBytes(charset).length));
return executeRequest(conn, body);
}
// ----- 通用 JSON POST -----
public String doPostJson(String url, String json) throws IOException {
HttpURLConnection conn = createConnection(new URL(url), "POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=" + charset);
return executeRequest(conn, json);
}
// ----- 核心執行邏輯 -----
private String executeRequest(HttpURLConnection conn, String requestBody) throws IOException {
try {
if (requestBody != null) {
try (OutputStream os = conn.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, charset)) {
osw.write(requestBody);
osw.flush();
}
}
int status = conn.getResponseCode();
if (status >= 300) {
// 讀取錯誤流以獲取伺服器錯誤詳情
String error = readStream(conn.getErrorStream());
throw new IOException("HTTP " + status + ": " + error);
}
return readStream(conn.getInputStream());
} finally {
conn.disconnect(); // 釋放連線資源
}
}
private String readStream(InputStream is) throws IOException {
if (is == null) return "";
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, charset))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
return sb.toString();
}
}
private HttpURLConnection createConnection(URL url, String method) throws IOException {
URLConnection uc;
if (proxyHost != null && proxyPort != null) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
uc = url.openConnection(proxy);
} else {
uc = url.openConnection();
}
HttpURLConnection conn = (HttpURLConnection) uc;
conn.setRequestMethod(method);
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
conn.setRequestProperty("Accept-Charset", charset);
conn.setRequestProperty("User-Agent", "Java HttpRequestor/1.0");
return conn;
}
private String buildFormBody(Map params) {
if (params == null || params.isEmpty()) return "";
StringBuilder sb = new StringBuilder();
for (Map.Entry e : params.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(encode(e.getKey())).append('=').append(encode(e.getValue()));
}
return sb.toString();
}
private String encode(String s) {
try { return URLEncoder.encode(s, charset); }
catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }
}
// ----- Setters -----
public void setCharset(String charset) { this.charset = charset; }
public void setConnectTimeout(Integer ms) { this.connectTimeout = ms; }
public void setReadTimeout(Integer ms) { this.readTimeout = ms; }
public void setProxy(String host, int port) { this.proxyHost = host; this.proxyPort = port; }
}
form = new HashMap<>();
form.put("username", "test");
form.put("password", "123456");
String result = new HttpRequestor().doPost("https://httpbin.org/post", form);
// POST JSON
String json = "{\"name\":\"test\",\"age\":25}";
String jsonResult = new HttpRequestor().doPostJson("https://httpbin.org/post", json);
// 設定代理與超時
HttpRequestor client = new HttpRequestor();
client.setProxy("proxy.company.com", 3128);
client.setConnectTimeout(3000);
client.setReadTimeout(10000);
關鍵最佳實務
| 實務 | 說明 |
|---|---|
| 必須設定超時 | 預設無限期阻塞,導致執行緒耗盡。connectTimeout (建立 TCP)、readTimeout (讀取資料)。 |
| try-with-resources 管理流 | 確保 InputStream/OutputStream 關閉,釋放連線回連線池 (keep-alive)。 |
| 讀取錯誤流 | 非 2xx 回應時,getErrorStream() 含伺服器錯誤訊息,getInputStream() 會拋異常。 |
| 指定 Charset | 請求/回應皆明確 UTF-8,避免平台預設編碼差異。 |
| 大檔案/流式處理 | 勿一次 readAllBytes()。使用 InputStream 逐塊處理或 Files.copy(is, path)。 |
| HTTPS 憑證信任 | 生產環境需正規 CA 憑證。測試環境可安裝自簽憑證到 cacerts 或實作 TrustManager (不建議生產用)。 |
| 連線重用 | HttpURLConnection 內建 Keep-Alive 連線池 (由 http.keepAlive, http.maxConnections 系統屬性控制)。 |
若需異步接收第三方回調 (如支付通知),Servlet 只需讀取 request.getInputStream() 即可獲取原始 Body,無需參數解析:
process(payload));
resp.setStatus(HttpServletResponse.SC_OK); // 立即回應 200
}
}
HttpURLConnection 雖 API 較繁瑣,但掌握其連線建立 → 設定屬性 → 輸出請求體 → 讀取回應碼/流 → 釋放資源 標準流程,能在無第三方庫環境下快速實現 HTTP 客戶端功能。生產系統長期維護建議遷移至 HttpClient 5 / OkHttp / WebClient,獲得連線池管理、重試、熔斷、HTTP/2、非阻塞 I/O 等企業級特性。