Apache HttpClient 經歷了 3.x(已停維)、4.5.x(長期支持)、5.x(現代非阻塞)三代。本文針對廣泛存量項目使用的 4.5.x 版本,系統講解 GET/POST 模擬、表單提交、編碼處理、重定向攔截、Cookie 管理、連接池優化,並提供生產級工具類封裝。新項目建議直接使用 HttpClient 5 或 OkHttp / Spring WebClient。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- 可選:HttpMime 用於 multipart 文件上傳 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpMethodParams;
import java.io.*;
public class HttpClientBasicDemo {
public static void main(String[] args) {
HttpClient client = new HttpClient();
client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));
// ----- GET 請求 -----
GetMethod get = new GetMethod("https://httpbin.org/get?key=value");
executeAndPrint(client, get);
// ----- POST 表單提交 -----
PostMethod post = new PostMethod("https://httpbin.org/post");
post.addParameter("username", "test_user");
post.addParameter("password", "secret123");
executeAndPrint(client, post);
}
private static void executeAndPrint(HttpClient client, HttpMethod method) {
try {
int status = client.executeMethod(method);
System.out.println("Status: " + status);
if (status == HttpStatus.SC_OK) {
try (InputStream is = method.getResponseBodyAsStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
} catch (HttpException | IOException e) {
e.printStackTrace();
} finally {
method.releaseConnection();
}
}
}
// 方式一:繼承 PostMethod 重寫 getRequestCharSet()
public static class Utf8PostMethod extends PostMethod {
public Utf8PostMethod(String url) { super(url); }
@Override public String getRequestCharSet() { return "UTF-8"; }
}
// 方式二:設置請求頭(推薦,更通用)
post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
client.getParams().setParameter(ClientPParams.HANDLE_REDIRECTS, false); // 關閉自動重定向
PostMethod post = new PostMethod("https://example.com/login");
post.addParameter("user", "admin");
post.addParameter("pass", "123456");
int status = client.executeMethod(post);
if (status == HttpStatus.SC_MOVED_TEMPORARILY) {
Header location = post.getResponseHeader("Location");
if (location != null) {
String redirectUrl = location.getValue();
// 關鍵:攜帶原請求的 Cookie 與 Referer
PostMethod redirectPost = new PostMethod(redirectUrl);
redirectPost.setRequestHeader("Referer", "https://example.com/login");
// 手動複製 Cookie(或使用 CookiePolicy 自動管理)
Header[] cookies = post.getResponseHeaders("Set-Cookie");
for (Header c : cookies) {
redirectPost.addRequestHeader(c.getName(), c.getValue());
}
client.executeMethod(redirectPost);
}
}
client.getParams().setParameter(ClientPParams.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
// 或更嚴格 RFC 2965
// client.getParams().setParameter(ClientPParams.COOKIE_POLICY, CookiePolicy.RFC_2965);
// 手動清除 Cookie
client.getState().clearCookies();
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public final class HttpClientUtil {
private static final HttpClient CLIENT;
private static final int TIMEOUT_MS = 10_000;
static {
CLIENT = new HttpClient();
// 連接池優化(多線程必須)
CLIENT.getHttpConnectionManager().getParams()
.setConnectionTimeout(TIMEOUT_MS)
.setSoTimeout(TIMEOUT_MS)
.setMaxTotalConnections(200)
.setDefaultMaxConnectionsPerHost(50);
// 重試策略:冪等方法自動重試 3 次
CLIENT.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, true));
// Cookie 策略
CLIENT.getParams().setParameter(ClientPParams.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
}
private HttpClientUtil() {}
/** GET 請求,返回字串 */
public static String get(String url, Map headers) throws HttpException, IOException {
GetMethod get = new GetMethod(url);
addHeaders(get, headers);
return execute(get);
}
/** POST 表單提交 */
public static String postForm(String url, Map formData, Map headers) throws HttpException, IOException {
PostMethod post = new Utf8PostMethod(url);
if (formData != null) {
formData.forEach(post::addParameter);
}
addHeaders(post, headers);
return execute(post);
}
/** POST JSON 提交 */
public static String postJson(String url, String jsonBody, Map headers) throws HttpException, IOException {
PostMethod post = new Utf8PostMethod(url);
post.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
post.setRequestBody(jsonBody);
addHeaders(post, headers);
return execute(post);
}
/** 文件上傳 */
public static String postMultipart(String url, Map fields, Map files, Map headers) throws HttpException, IOException {
PostMethod post = new PostMethod(url);
Part[] parts = buildMultipartParts(fields, files);
post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
addHeaders(post, headers);
return execute(post);
}
private static String execute(HttpMethod method) throws HttpException, IOException {
try {
int status = CLIENT.executeMethod(method);
if (status >= 400) {
throw new HttpException("HTTP " + status + ": " + method.getStatusLine());
}
try (InputStream is = method.getResponseBodyAsStream()) {
return is != null ? IOUtils.toString(is, StandardCharsets.UTF_8) : "";
}
} finally {
method.releaseConnection();
}
}
private static void addHeaders(HttpMethod method, Map headers) {
if (headers != null) {
headers.forEach(method::setRequestHeader);
}
}
private static Part[] buildMultipartParts(Map fields, Map files) {
// 實現略:StringPart + FilePart 組合
return new Part[0];
}
// 內部 UTF-8 PostMethod
private static class Utf8PostMethod extends PostMethod {
public Utf8PostMethod(String url) { super(url); }
@Override public String getRequestCharSet() { return "UTF-8"; }
}
}
public class ClientDemo {
public static void main(String[] args) {
try {
// GET with headers
String html = HttpClientUtil.get("https://httpbin.org/headers", Map.of(
"User-Agent", "Mozilla/5.0 (Java HttpClient)",
"Accept", "text/html"
));
System.out.println(html);
// POST form
String resp = HttpClientUtil.postForm("https://httpbin.org/post", Map.of(
"action", "login",
"user", "admin"
), null);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 中文參數亂碼 | 請求體編碼非 UTF-8 | 使用 Utf8PostMethod 或設置 Content-Type charset |
| 連接超時/讀取超時 | 網絡波動、服務端處理慢 | 調大 setSoTimeout、啟用重試、熔斷降級 |
| 連接洩漏導致無可用連接 | 未調用 releaseConnection() | finally 塊強制釋放、使用連接池監控 |
| HTTPS 报 SSLHandshakeException | 憑證不信任、主機名驗證失敗 | 生產環境導入正規 CA 憑證;測試環境信任所有(不推薦生產) |
| 多線程下狀態錯亂 | HttpClient 3.x 非線程安全 | 升級 HttpClient 4.5+ / 5.x,或每線程獨享實例 |
import org.apache.commons.httpclient.protocol.*;
import javax.net.ssl.*;
Protocol easyHttps = new Protocol("https", new ProtocolSocketFactory() {
@Override public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException {
return SSLContext.getDefault().getSocketFactory().createSocket(host, port);
}
@Override public Socket createSocket(String host, int port) throws IOException {
return SSLContext.getDefault().getSocketFactory().createSocket(host, port);
}
}, 443);
Protocol.registerProtocol("https", easyHttps);
// 全局生效,慎用!
存量項目可保持 4.5.x 維護,新模組建議直接上 5.x 或 OkHttp,避免技術債積累。
HttpClient 4.5 雖屬上一代,但憑藉成熟穩定、文檔豐富、生態完善,仍大量服役於企業級系統。掌握其連接池調優、編碼控制、重定向與 Cookie 精細管理、異常重試策略,足以應對 95% 的 HTTP 集成場景。本文提供的 HttpClientUtil 封裝了核心最佳實踐,可直接投入生產使用。