在 Java 中發送 HTTP 請求是系統整合、爬蟲、第三方 API 調用的基礎技能。從 JDK 內建的 `HttpURLConnection`、Apache HttpClient、OkHttp 到 JDK 11+ 的 `HttpClient`,選型與封裝直接影響代碼可維護性與穩定性。本文以**原生 HttpURLConnection 手寫 GET/POST** 為基礎,進階至**通用請求封裝類**,並對比主流客戶端庫選型建議。
public static String doGet(String urlString, Map headers) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
// 設置請求頭
if (headers != null) headers.forEach(conn::setRequestProperty);
// 處理響應
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();
} finally {
conn.disconnect();
}
}
public static String doPost(String urlString, String body, String contentType, Map headers) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true); // 允許輸出流
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setRequestProperty("Content-Type", contentType); // application/json / application/x-www-form-urlencoded
if (headers != null) headers.forEach(conn::setRequestProperty);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
os.flush();
}
int code = conn.getResponseCode();
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) sb.append(line);
if (code >= 400) throw new HttpException(code, sb.toString());
return sb.toString();
} finally {
conn.disconnect();
}
}
public class HttpClientUtil {
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 15000;
// 鏈式調用構建器
public static class RequestBuilder {
private String url;
private String method = "GET";
private Map headers = new HashMap<>();
private Map params = new LinkedHashMap<>(); // GET 參數 / POST 表單
private String jsonBody; // JSON 字串
private String contentType = "application/x-www-form-urlencoded";
public RequestBuilder url(String url) { this.url = url; return this; }
public RequestBuilder method(String method) { this.method = method; return this; }
public RequestBuilder header(String k, String v) { headers.put(k, v); return this; }
public RequestBuilder param(String k, Object v) { params.put(k, v); return this; }
public RequestBuilder jsonBody(String json) { this.jsonBody = json; this.contentType = "application/json"; return this; }
public RequestBuilder formParams(Map params) { this.params.putAll(params); return this; }
public String execute() throws IOException {
// 1. 構建最終 URL (GET 參數拼接)
String finalUrl = url;
if (!params.isEmpty() && ("GET".equalsIgnoreCase(method) || jsonBody == null)) {
String query = params.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(String.valueOf(e.getValue()), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
finalUrl += (url.contains("?") ? "&" : "?") + query;
}
// 2. 開啟連線
HttpURLConnection conn = (HttpURLConnection) new URL(finalUrl).openConnection();
conn.setRequestMethod(method);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
headers.forEach(conn::setRequestProperty);
// 3. 處理請求體 (POST/PUT/PATCH)
if (("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) && jsonBody != null) {
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", contentType);
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
}
} else if (("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && !params.isEmpty() && jsonBody == null) {
// 表單提交
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
String formBody = params.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(String.valueOf(e.getValue()), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
try (OutputStream os = conn.getOutputStream()) {
os.write(formBody.getBytes(StandardCharsets.UTF_8));
}
}
// 4. 讀取響應
int code = conn.getResponseCode();
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String body = br.lines().collect(Collectors.joining("\n"));
if (code >= 400) throw new HttpException(code, body, headers);
return body;
} finally {
conn.disconnect();
}
}
}
// 靜態入口
public static RequestBuilder create() { return new RequestBuilder(); }
// 異常類
public static class HttpException extends IOException {
private final int statusCode;
private final Map responseHeaders;
public HttpException(int code, String message) { this(code, message, null); }
public HttpException(int code, String message, Map headers) {
super("HTTP " + code + ": " + message);
this.statusCode = code;
this.responseHeaders = headers;
}
public int getStatusCode() { return statusCode; }
public Map getResponseHeaders() { return responseHeaders; }
}
}
// GET 請求帶參數與 Header
String getResp = HttpClientUtil.create()
.url("https://api.example.com/users")
.method("GET")
.param("page", 1)
.param("size", 20)
.header("Authorization", "Bearer token123")
.execute();
// POST JSON
String json = "{\"name\":\"Alice\",\"age\":25}";
String postResp = HttpClientUtil.create()
.url("https://api.example.com/users")
.method("POST")
.jsonBody(json)
.header("Authorization", "Bearer token123")
.execute();
// POST 表單
String formResp = HttpClientUtil.create()
.url("https://api.example.com/login")
.method("POST")
.param("username", "admin")
.param("password", "123456")
.execute();
| 庫 | 版本要求 | 核心優勢 | 適用場景 |
|---|---|---|---|
| HttpURLConnection | JDK 1.1+ | 零依賴、標準 API、輕量 | 簡單調用、受限環境無法引入第三方 Jar、Android 早期版本 |
| Apache HttpClient 4.5/5.x | JDK 1.7+ | 連線池、重試、攔截器、多部分上傳、成熟穩定 | 企業級應用、複雜 HTTP 交互、需連線池管理、Spring RestTemplate 底層 |
| OkHttp 3.x/4.x | JDK 1.8+ | HTTP/2、連線池、透明 GZIP、攔截器鏈、同步/異步、Kotlin 友好 | 高性能要求、Android 主流、Retrofit 底層、微服務調用 |
| JDK 11+ HttpClient | JDK 11+ | 標準化、HTTP/2、WebSocket、CompletableFuture 異步、零依賴 | JDK 11+ 專案、不想引入第三方、現代化異步編程 |
| Spring WebClient / RestTemplate | Spring 5+ | 聲明式、響應式 (WebClient)、整合 Spring 生態、負載均衡、熔斷 | Spring Boot / Spring Cloud 微服務調用 |
理解 `HttpURLConnection` 底層原理是掌握高級客戶端的前提。實務中:**新專案 JDK 11+ 首選標準 `HttpClient` (零依賴、現代 API);Spring Boot 專案直接用 `WebClient`/`RestTemplate`;高性能/微服務/Andorid 選 `OkHttp`;老舊企業級應用維護用 `HttpClient`**。封裝統一 `HttpClientUtil` 介面,隔離底層實現,才能靈活遷移與演進。