生產環境常需調用第三方 HTTPS 接口。Apache HttpClient 4.5+ 提供靈活的 SSL 上下文構建器,本文提供生產就緒的工具類代碼,涵蓋:信任所有證書(測試環境)、連接/讀取/請求三層超時、響應實體消費、連接管理器釋放、異常處理與日志記錄。
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.X509Certificate;
public class HttpsClientUtil {
private static final int TIMEOUT_MS = 5000;
private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MS)
.setSocketTimeout(TIMEOUT_MS)
.setConnectionRequestTimeout(TIMEOUT_MS)
.build();
/** 獲取 HTTPS 響應文本(信任所有證書,僅測試環境使用) */
public static String getSSL(String url, String responseCharset) throws IOException {
CloseableHttpClient httpClient = createTrustAllClient();
CloseableHttpResponse response = null;
try {
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(REQUEST_CONFIG);
response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
String content = EntityUtils.toString(entity, responseCharset);
EntityUtils.consume(entity); // 釋放連接回池
return content;
}
return null;
} catch (IOException e) {
// log.error("HTTPS request failed: {}", url, e);
throw e;
} finally {
closeQuietly(response);
closeQuietly(httpClient);
}
}
/** 構建信任所有證書的 HttpClient(生產環境請載入具體信任庫) */
private static CloseableHttpClient createTrustAllClient() {
try {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (TrustStrategy) (X509Certificate[] chain, String authType) -> true)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(REQUEST_CONFIG)
.build();
} catch (Exception e) {
// log.warn("SSL context init failed, fallback to default", e);
return HttpClients.createDefault();
}
}
private static void closeQuietly(AutoCloseable closeable) {
if (closeable != null) {
try { closeable.close(); } catch (Exception ignored) {}
}
}
// POST 範例
public static String postSSL(String url, String jsonBody, String charset) throws IOException {
CloseableHttpClient client = createTrustAllClient();
CloseableHttpResponse resp = null;
try {
HttpPost post = new HttpPost(url);
post.setConfig(REQUEST_CONFIG);
post.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
resp = client.execute(post);
HttpEntity entity = resp.getEntity();
return entity != null ? EntityUtils.toString(entity, charset) : null;
} finally {
closeQuietly(resp);
closeQuietly(client);
}
}
}
| 配置項 | 含義 | 建議值 |
|---|---|---|
setConnectTimeout | 建立 TCP 連接超時 | 5000ms |
setSocketTimeout | 讀取數據超時 (SO_TIMEOUT) | 5000ms |
setConnectionRequestTimeout | 從連接池獲取連接超時 | 5000ms |
TrustStrategy → true | 跳過證書驗證(自簽/測試環境) | 僅測試環境 |
EntityUtils.consume() | 確保實體流讀完,連接釋放回池 | 必須調用 |
⚠️ 切勿在生產環境使用「信任所有證書」! 應採用以下任一方式:
SSLContextBuilder().loadTrustMaterial(new File("truststore.jks"), "changeit".toCharArray())
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, new String[]{"TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version> <!-- 或 4.5.x 最新 -->
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
</dependency>
EntityUtils.consume(entity) 或完整讀取流RequestConfig 同時應用於 Client 默認與 Request 級別PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200); // 最大連接數
cm.setDefaultMaxPerRoute(50); // 每路由最大連接
cm.setValidateAfterInactivity(2000); // 空閒 2s 後驗證有效性
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultRequestConfig(REQUEST_CONFIG)
.evictIdleConnections(30, TimeUnit.SECONDS) // 定時清理空閒
.build();