Apache HttpComponents 常用 API 与示例详解

以真实可运行示例详解 Apache HttpComponents 的核心 API 用法——HttpGet/HttpPost、PoolingHttpClientConnectionManager 连接池、RequestConfig 超时、状态码处理与连接释放,并给出 HttpClient4/5 的完整源码与实测回显。

Apache HttpComponentsApache HttpComponentsJavaApache2026/10/11

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 / HttpClientsCloseableHttpClient / HttpClients发起请求并返回响应
GET 请求HttpGetHttpGet发起 GET
POST 请求HttpPost / UrlEncodedFormEntityHttpPost / UrlEncodedFormEntity发起表单 POST
连接池PoolingHttpClientConnectionManagerPoolingHttpClientConnectionManagerBuilder连接复用/并发管理
请求配置RequestConfigorg.apache.hc.client5.http.config.RequestConfig超时/重定向/代理
响应/实体CloseableHttpResponse / HttpEntity / EntityUtilsCloseableHttpResponse / 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)。

七、参考链接