雖然現代項目多使用 HttpClient、OkHttp、WebClient 等成熟庫,但理解 JDK 原生 HttpURLConnection 的底層交互細節,對排查網絡問題、適配特殊環境(如無法引入第三方依賴的舊項目)仍有重要價值。本文提供一個生產可用的工具類,涵蓋 GET、POST、HTTPS、代理、超時控制、資源釋放等核心場景。
public class HttpRequestUtil {
// ===== 代理配置(可外部化注入)=====
private static boolean proxyEnabled = false;
private static String proxyHost = "127.0.0.1";
private static int proxyPort = 8087;
public static void setProxy(String host, int port) {
proxyEnabled = true;
proxyHost = host;
proxyPort = port;
}
// ===== 通用異步執行入口 =====
private static HttpURLConnection openConnection(String urlStr, String method, boolean useProxy) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection conn;
if (useProxy && proxyEnabled) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
conn = (HttpURLConnection) url.openConnection(proxy);
} else {
conn = (HttpURLConnection) url.openConnection();
}
conn.setRequestMethod(method);
conn.setConnectTimeout(10_000); // 連接超時 10s
conn.setReadTimeout(30_000); // 讀取超時 30s
conn.setUseCaches(false);
// 通用請求頭
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java HttpClient)");
return conn;
}
// ===== 讀取響應流通用邏輯 =====
private static String readResponse(HttpURLConnection conn) throws IOException {
try (InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
return sb.toString();
} catch (IOException e) {
// 嘗試讀取錯誤流
try (InputStream es = conn.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(es, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
throw new IOException("HTTP " + conn.getResponseCode() + ": " + sb.toString(), e);
}
}
}
// ===== 公開 API =====
/** 發送 GET 請求,參數自動拼接到 URL */
public static String sendGet(String baseUrl, Map<String, String> params, boolean useProxy) {
String url = buildUrlWithParams(baseUrl, params);
try {
HttpURLConnection conn = openConnection(url, "GET", useProxy);
conn.connect();
return readResponse(conn);
} catch (IOException e) {
throw new RuntimeException("GET request failed: " + url, e);
}
}
/** 發送 POST 請求,表單數據寫入請求體 */
public static String sendPost(String url, String formData, boolean useProxy) {
try {
HttpURLConnection conn = openConnection(url, "POST", useProxy);
conn.setDoOutput(true); // 關鍵:允許輸出流
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
conn.connect();
try (OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8)) {
osw.write(formData);
osw.flush();
}
return readResponse(conn);
} catch (IOException e) {
throw new RuntimeException("POST request failed: " + url, e);
}
}
/** 發送 POST JSON 請求 */
public static String sendPostJson(String url, String jsonBody, boolean useProxy) {
try {
HttpURLConnection conn = openConnection(url, "POST", useProxy);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.connect();
try (OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8)) {
osw.write(jsonBody);
osw.flush();
}
return readResponse(conn);
} catch (IOException e) {
throw new RuntimeException("POST JSON request failed: " + url, e);
}
}
/** 下載文件/圖片返回輸入流(需調用者關閉) */
public static InputStream downloadStream(String url, boolean useProxy) throws IOException {
HttpURLConnection conn = openConnection(url, "GET", useProxy);
conn.connect();
if (conn.getResponseCode() != 200) {
throw new IOException("Download failed: HTTP " + conn.getResponseCode());
}
return conn.getInputStream(); // 注意:調用者必須在 finally 中關閉流並調用 conn.disconnect()
}
// ===== 輔助方法 =====
private static String buildUrlWithParams(String baseUrl, Map<String, String> params) {
if (params == null || params.isEmpty()) return baseUrl;
StringBuilder sb = new StringBuilder(baseUrl);
sb.append(baseUrl.contains("?") ? "&" : "?");
params.forEach((k, v) -> {
try {
sb.append(URLEncoder.encode(k, "UTF-8")).append('=')
.append(URLEncoder.encode(v, "UTF-8")).append('&');
} catch (UnsupportedEncodingException ignored) {}
});
sb.deleteCharAt(sb.length() - 1); // 移除最後的 &
return sb.toString();
}
}
String html = HttpRequestUtil.sendGet("https://api.example.com/users",
Map.of("page", "1", "size", "20"), false);
System.out.println(html);
String form = "username=admin&password=123456";
String result = HttpRequestUtil.sendPost("https://api.example.com/login", form, false);
System.out.println(result);
String json = "{\"name\":\"張三\",\"age\":25}";
String resp = HttpRequestUtil.sendPostJson("https://api.example.com/users", json, false);
HttpRequestUtil.setProxy("127.0.0.1", 8888); // 設置代理
String viaProxy = HttpRequestUtil.sendGet("https://httpbin.org/ip", null, true);
System.out.println("代理出口 IP: " + viaProxy);
try (InputStream is = HttpRequestUtil.downloadStream("https://example.com/logo.png", false);
FileOutputStream fos = new FileOutputStream("logo.png")) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) != -1) fos.write(buffer, 0, len);
} catch (IOException e) {
log.error("下載失敗", e);
}
setConnectTimeout(建立連接)+ setReadTimeout(讀取數據),防止線程無限掛起。getErrorStream() 獲取服務端錯誤詳情,否則連接無法復用。StandardCharsets.UTF_8 避免平台默認編碼不一致導致亂碼。Proxy.Type.HTTP 適用 HTTP/HTTPS 隧道;SOCKS5 代理用 Proxy.Type.SOCKS。TrustManager,應導入正規 CA 憑證或配置信任庫。新項目強烈推薦使用 Apache HttpClient 5.x(連接池、重試、攔截器、異步支持完善)或 OkHttp(HTTP/2、連接復用、現代 API 設計)。僅在極度受限環境(如 JDK 8 無依賴、嵌入式設備)下使用原生 HttpURLConnection。