Apache HttpComponents 常用 API 与示例详解
以真实可运行示例详解 Apache HttpComponents 的核心 API 用法——HttpGet/HttpPost、PoolingHttpClientConnectionManager 连接池、RequestConfig 超时、状态码处理与连接释放,并给出 HttpClient4/5 的完整源码与实测回显。
Apache HttpComponents 常用 API 与示例详解
目标:HttpComponents 是 Java 库而非提供 CLI 的工具集,因此本「命令详解」篇以 API 用法详解 呈现——覆盖 HttpGet/HttpPost、连接池、超时配置、状态码处理、连接释放与 HttpClientBuilder 构建 6 类核心用法,给出 HttpClient 4.5.14 与 5.4.1 的完整可运行源码,并附真实请求 httpbin.org 与本地 HTTP 服务的回显。已在 Ubuntu 22.04、JDK 11 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS,JVM:OpenJDK 11(11.0.32.1)
- HttpClient 4.x:4.5.14(HttpCore 4.4.16)
- HttpClient 5.x:5.4.1(HttpCore 5.3.1)
- 构建:Maven 3.6.3 +
maven-shade-plugin打 fat-jar - 示例工程:/opt/hc4(HttpClient4)、/opt/hc5(HttpClient5)
- 回显服务:httpbin.org(公网)、
python -m http.server本地 18080 端口
二、工具概览
「工具」对应 HttpComponents 的核心 API 类型:
| 类别 | HttpClient 4.x 关键类 | HttpClient 5.x 关键类 | 用途 |
|---|---|---|---|
| 同步客户端 | CloseableHttpClient / HttpClients | CloseableHttpClient / HttpClients | 发起请求并返回响应 |
| GET 请求 | HttpGet | HttpGet | 发起 GET |
| POST 请求 | HttpPost / UrlEncodedFormEntity | HttpPost / UrlEncodedFormEntity | 发起表单 POST |
| 连接池 | PoolingHttpClientConnectionManager | PoolingHttpClientConnectionManagerBuilder | 连接复用/并发管理 |
| 请求配置 | RequestConfig | org.apache.hc.client5.http.config.RequestConfig | 超时/重定向/代理 |
| 响应/实体 | CloseableHttpResponse / HttpEntity / EntityUtils | CloseableHttpResponse / HttpEntity / EntityUtils | 读取状态与正文 |
三、前置条件
- 已搭建 JDK 11 + Maven 3.6.3 并引入依赖(见《Ubuntu 22.04 安装 Apache HttpComponents 指南》)。
- 示例可直接
mvn package后java -jar运行;连接公网需可访问 httpbin.org。
四、API 详解
4.1 HttpGet 请求
用途:发起 GET 请求并得到状态码与正文。完整源码(可并入项目编译):
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.apache.http.HttpEntity;
import java.nio.charset.StandardCharsets;
HttpGet get = new HttpGet("http://httpbin.org/get?name=hc4&version=4.5.14");
get.setHeader("Accept", "application/json");
CloseableHttpResponse resp = client.execute(get); // 真实回显见 4.5
HttpEntity entity = resp.getEntity();
System.out.println("状态码=" + resp.getStatusLine().getStatusCode());
System.out.println(EntityUtils.toString(entity, StandardCharsets.UTF_8));
resp.close(); // 必须释放连接
4.2 HttpPost 请求
用途:以表单提交 POST,携带键值对。HttpClient 4.x 写法:
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import java.util.Arrays;
HttpPost post = new HttpPost("http://httpbin.org/post");
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("name", "hc4"),
new BasicNameValuePair("method", "POST")), StandardCharsets.UTF_8));
HttpClient 5.x 写法(API 基本一致,包名不同 org.apache.hc.client5.http.entity):
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import java.util.Arrays;
HttpPost post = new HttpPost("https://httpbin.org/post");
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("tool", "httpclient5"),
new BasicNameValuePair("method", "POST")), StandardCharsets.UTF_8));
4.3 连接管理(PoolingHttpClientConnectionManager)
用途:控制并发连接、连接复用与限制。HttpClient 4.x:
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(50);
cm.setDefaultMaxPerRoute(10);
HttpClient 5.x(用 Builder):
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(50)
.setMaxConnPerRoute(10)
.build();
4.4 请求配置(RequestConfig)
用途:集中设置连接/读取超时与重定向。HttpClient 4.x:
import org.apache.http.client.config.RequestConfig;
RequestConfig cfg = RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(8000)
.build();
HttpClient 5.x:
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.core5.util.Timeout;
RequestConfig cfg = RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofSeconds(5))
.setConnectTimeout(Timeout.ofSeconds(5))
.setResponseTimeout(Timeout.ofSeconds(8))
.build();
4.5 客户端构建与完整运行示例
HttpClient 4.x 完整示例
主类 /opt/hc4/src/main/java/com/example/HttpClient4Demo.java:
package com.example;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
public class HttpClient4Demo {
public static void main(String[] args) throws Exception {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(50);
cm.setDefaultMaxPerRoute(10);
RequestConfig rc = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(5000)
.setSocketTimeout(8000).build();
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm).setDefaultRequestConfig(rc).build();
try {
HttpGet get = new HttpGet("http://httpbin.org/get?name=hc4&version=4.5.14");
get.setHeader("Accept", "application/json");
CloseableHttpResponse resp = client.execute(get);
try {
System.out.println("[HttpGet] 状态码=" + resp.getStatusLine().getStatusCode()
+ " 协议=" + resp.getProtocolVersion()
+ " 原因=" + resp.getStatusLine().getReasonPhrase());
System.out.println(EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8));
} finally {
resp.close(); // 状态码处理 + 连接释放
}
HttpPost post = new HttpPost("http://httpbin.org/post");
post.setEntity(new org.apache.http.client.entity.UrlEncodedFormEntity(
java.util.Arrays.asList(
new org.apache.http.message.BasicNameValuePair("name", "hc4"),
new org.apache.http.message.BasicNameValuePair("method", "POST")),
StandardCharsets.UTF_8));
CloseableHttpResponse postResp = client.execute(post);
try {
System.out.println("[HttpPost] 状态码=" + postResp.getStatusLine().getStatusCode());
System.out.println(EntityUtils.toString(postResp.getEntity(), StandardCharsets.UTF_8));
} finally {
postResp.close();
}
System.out.println("连接池: MaxTotal=" + cm.getMaxTotal()
+ " 已分配=" + cm.getTotalStats().getLeased()
+ " 可用=" + cm.getTotalStats().getAvailable());
} finally {
client.close();
}
}
}
编译运行:
cd /opt/hc4
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 mvn package
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 /usr/lib/jvm/java-11-openjdk-amd64/bin/java \
-Xms256m -Xmx256m -Xmn128m -Dfile.encoding=UTF-8 -jar target/hc4-demo-1.0.0.jar
真实回显:
[HttpGet] URL=http://httpbin.org/get?name=hc4&version=4.5.14
[HttpGet] 状态码=200 协议=HTTP/1.1 原因=OK
[HttpGet] Content-Type=Content-Type: application/json
[HttpGet] 响应体:
{
"args": {
"name": "hc4",
"version": "4.5.14"
},
"headers": {
"Accept": "application/json",
"Accept-Encoding": "gzip,deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/4.5.14 (Java/11.0.32.1)",
"X-Amzn-Trace-Id": "Root=1-6acb2191-1f8a167e01dee2e418a4d463"
},
"origin": "42.63.89.89",
"url": "http://httpbin.org/get?name=hc4&version=4.5.14"
}
[HttpPost] 状态码=200
[HttpPost] 响应体(form 提交回显):
{
"args": {},
"data": "",
"files": {},
"form": {
"method": "POST",
"name": "hc4"
},
"headers": {
"Accept-Encoding": "gzip,deflate",
"Content-Length": "20",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/4.5.14 (Java/11.0.32.1)",
"X-Amzn-Trace-Id": "Root=1-6acb2191-2ef096882c1263de5ff49a8a"
},
"json": null,
"origin": "42.63.89.89",
"url": "http://httpbin.org/post"
}
连接池统计: MaxTotal=50 已分配=0 可用=1 待释放=0
HttpClient 5.x 完整示例
主类 /opt/hc5/src/main/java/com/example/HttpClient5Demo.java(用 HttpClientBuilder 形态,即 HttpClients.custom()):
package com.example;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.util.Timeout;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class HttpClient5Demo {
public static void main(String[] args) throws Exception {
PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(50).setMaxConnPerRoute(10).build();
RequestConfig rc = RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofSeconds(5))
.setConnectTimeout(Timeout.ofSeconds(5))
.setResponseTimeout(Timeout.ofSeconds(8)).build();
CloseableHttpClient client = HttpClients.custom()
.setConnectionManager(cm).setDefaultRequestConfig(rc).build();
try {
HttpGet get = new HttpGet("https://httpbin.org/get?name=hc5&version=5.4.1");
get.addHeader("Accept", "application/json");
CloseableHttpResponse resp = client.execute(get);
System.out.println("[HttpGet] 状态码=" + resp.getCode());
System.out.println(EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8));
resp.close();
HttpPost post = new HttpPost("https://httpbin.org/post");
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("tool", "httpclient5"),
new BasicNameValuePair("method", "POST")), StandardCharsets.UTF_8));
CloseableHttpResponse postResp = client.execute(post);
System.out.println("[HttpPost] 状态码=" + postResp.getCode());
System.out.println(EntityUtils.toString(postResp.getEntity(), StandardCharsets.UTF_8));
postResp.close();
System.out.println("连接池: MaxTotal=" + cm.getTotalStats().getMax()
+ " 已分配Leased=" + cm.getTotalStats().getLeased()
+ " 可用Available=" + cm.getTotalStats().getAvailable());
} finally {
client.close();
}
}
}
编译运行:
cd /opt/hc5
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 mvn package
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 /usr/lib/jvm/java-11-openjdk-amd64/bin/java \
-Xms256m -Xmx256m -Xmn128m -Dfile.encoding=UTF-8 -jar target/hc5-demo-1.0.0.jar
真实回显:
[HttpGet] URL=https://httpbin.org/get?name=hc5&version=5.4.1
[HttpGet] 状态码=200
[HttpGet] Content-Type=Content-Type: application/json
[HttpGet] 响应体:
{
"args": {
"name": "hc5",
"version": "5.4.1"
},
"headers": {
"Accept": "application/json",
"Accept-Encoding": "gzip, x-gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.4.1 (Java/11.0.32.1)",
"X-Amzn-Trace-Id": "Root=1-6acb21b6-549e5159059c5050082137e5"
},
"origin": "42.63.89.89",
"url": "https://httpbin.org/get?name=hc5&version=5.4.1"
}
[HttpPost] 状态码=200
[HttpPost] 响应体:
{
"args": {},
"data": "",
"files": {},
"form": {
"method": "POST",
"tool": "httpclient5"
},
"headers": {
"Accept-Encoding": "gzip, x-gzip, deflate",
"Content-Length": "28",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "httpbin.org",
"User-Agent": "Apache-HttpClient/5.4.1 (Java/11.0.32.1)",
"X-Amzn-Trace-Id": "Root=1-6acb21b6-058e71a902916b616b14ed9d"
},
"json": null,
"origin": "42.63.89.89",
"url": "https://httpbin.org/post"
}
连接池: MaxTotal=50 已分配Leased=0 可用Available=1
4.6 状态码处理与连接释放
- 状态码:4.x 用
resp.getStatusLine().getStatusCode()(及getReasonPhrase());5.x 用resp.getCode()。判断200~299为成功,按需对3xx/4xx/5xx分支。 - 连接释放:响应对象必须在
finally中close()。释放时机:先EntityUtils.toString(entity, UTF_8)消费完实体,再resp.close()归还连接到连接池。示例用 try 加 finally 结构,保证异常时也能释放。 - 客户端关闭:进程结束时
client.close()关闭连接池所有连接。
4.7 请求本地 HTTP 服务回显
内网/本机场景可先起一个本地 HTTP 服务,再用 HttpClient 请求。启动本地回显(端口 18080):
mkdir -p /tmp/www
echo "Hello from local HTTP server - port 18080" > /tmp/www/index.html
cd /tmp/www
nohup python3 -m http.server 18080 > /tmp/httpserver.log 2>&1 &
HttpClient 4.x 请求本地服务,主类 LocalGet(GET file):
package com.example;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
public class LocalGet {
public static void main(String[] args) throws Exception {
RequestConfig cfg = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(5000)
.setSocketTimeout(8000).build();
CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(cfg).build();
try {
HttpGet get = new HttpGet("http://127.0.0.1:18080/index.html");
CloseableHttpResponse resp = client.execute(get);
try {
HttpEntity entity = resp.getEntity();
System.out.println("状态码=" + resp.getStatusLine().getStatusCode()
+ " 原因=" + resp.getStatusLine().getReasonPhrase());
System.out.println("响应体=" + EntityUtils.toString(entity, StandardCharsets.UTF_8).trim());
} finally {
resp.close();
}
} finally {
client.close();
}
}
}
编译并运行(复用 /opt/hc4 工程,jar 内含 LocalGet):
cd /opt/hc4
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 mvn package -q
JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 /usr/lib/jvm/java-11-openjdk-amd64/bin/java \
-Dfile.encoding=UTF-8 -cp target/hc4-demo-1.0.0.jar com.example.LocalGet
真实回显:
状态码=200 原因=OK
响应体=Hello from local HTTP server - port 18080
本地服务访问日志(真实回显):
127.0.0.1 - - [11/Oct/2026 05:43:04] "GET /index.html HTTP/1.1" 200 -
127.0.0.1 - - [11/Oct/2026 05:43:09] "GET /index.html HTTP/1.1" 200 -
五、注意事项
- 资源释放优先:所有
CloseableHttpResponse一律finally关闭,推荐 try-with-resources 便于结合连接管理。 - 实体消费:Response 关闭前需消费完
getEntity();用EntityUtils.toString()(或consumeEntity)一次性取全正文。 - 超时必配:不设超时隔空请求会长时间挂起;HttpClient 5.x 的
setResponseTimeout尤其重要(默认禁用)。 - 编码:中文/JSON 场景用
StandardCharsets.UTF_8,避免平台默认编码不一致。
六、常见问题
Q1:并发下报 ConnectionPoolTimeoutException
连接池不够或 connectionRequestTimeout 过短。调大 setMaxTotal/setDefaultMaxPerRoute,并把取连接超时设为 3~5s。
Q2:从一个线程拿的连接在另一线程用会怎样?
理论上可复用但连接释放时机跨线程易出错;建议每个请求上下文在同一线程完成「获取→执行→释放」。
Q3:读取响应报 StreamReset?
多为服务端提前关闭或代理断连;可与 status 判断、重试策略配合,并保证实体被完整消费后再 close。
Q4:GET/POST 中文参数乱码?
URL 参数需 URL 编码;表单用 UrlEncodedFormEntity(参数List, UTF_8);读取用 EntityUtils.toString(entity, UTF_8)。
七、参考链接
- Apache HttpClient 4.5.x 快速入门:https://hc.apache.org/httpcomponents-client-4.5.x/quickstart.html
- Apache HttpClient 5.x 官方文档:https://hc.apache.org/httpcomponents-client-5.4.x/
- HttpClient 5.x 同步示例:https://hc.apache.org/httpcomponents-client-5.4.x/examples.html
- 连接管理参考:https://hc.apache.org/httpcomponents-client-5.4.x/httpclient/apidocs/