HttpClient

Lu Lv3

HttpClient

简介

HttpClient 是Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新版本。我们可以使用HttpClient发送各种HTP方法。

主要特性

  1. 支持HTTP和HTTPS:实现HTTP1.0/1.1,并支持加密的HTTPS协议(SSL)。
  2. 认证方案:支持多种认证方式(如Basic, Digest, NTLM)及自定义插件认证。
  3. 连接管理:支持多线程应用,最大连接数设置和过期连接自动关闭。
  4. 自动处理Cookies:自动管理Set-Cookie头并支持自定义Cookie策略。
  5. 流优化和持久连接:优化请求/响应流,支持HTTP的持久连接(Keep-Alive)。

简单使用

HttpGet请求响应的一般步骤:

  • 创建HttpClient对象,可以使用 HttpClients.createDefault();

  • 创建Http请求对象:

    • 如果是无参数的GET请求,则直接使用构造方法 HttpGet(String url) 创建 HttpGet 对象即可。
    • 如果是带参数的GET请求,则可以先使用 URIBuilder(String url) 创建对象,再调用 addParameter(String param, String value) ,或 setParameter(String param, String value) 来设置请求参数,并调用 build() 方法构建一个URI 对象。只有构造方法 HttpGet(URI uri) 来构建 HttpGet 对象。
  • 创建 HttpResponse,调用 HttpClient 对象的 execute(HttpUriRequest request) 发送请求,该方法返回一个 HttpResponse。调用 HttpResponse 的 getAllHeaders()、getHeaders(String name) 等方法可获取服务器的响应头;调用 HttpResponse 的 getEntity() 方法可以获取 HttpEntity 对象,该对象包装了服务器的响应内容;调用 HttpResponse 的 getStatusLine().getStatusCode()获取响应状态码。

  • 释放连接,包括 HttpResponse、HttpClient

HttpPost请求响应的一般步骤:

  • 创建HttpClient对象,可以使用 HttpClients.createDefault();
  • 创建Http请求对象:
    • 如果是无参数的POST请求,则直接通过构造方法 HttpPost(String uri) 创建 HttpPost 对象即可。
    • 如果是带参数的POST请求,在创建出 HttpPost对象之后,还需要构建一个 HttpEntity 对象(如 StringEntity, UrlEncodedFormEntity ……entity接口的实现类)设置请求参数,然后调用 HttpPost.setEntity(HttpEntity entity)HttpEntity 对象添加到请求中。
  • 创建HttpResponse ,调用 HttpClient 对象的 execute(HttpUriRequest request) 发送请求,该方法返回一个 HttpResponse。调用 HttpResponse 的 getAllHeaders()、getHeaders(String name) 等方法可获取服务器的响应头;调用 HttpResponse 的 getEntity() 方法可以获取 HttpEntity 对象,该对象包装了服务器的响应内容;调用 HttpResponse 的 getStatusLine().getStatusCode()获取响应状态码。
  • 释放连接,包括 HttpResponse、HttpClient

使用教程

  1. 引入Maven依赖
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.5</version> <!-- 请使用最新的稳定版本 -->
</dependency>
<!-- Jackson 依赖,用于 JSON 处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
  1. Java代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class HttpClientExamples {
public static void main(String[] args) throws Exception {
// 无参数 GET 请求
sendGetRequestNoParams();
// 带参数 GET 请求
sendGetRequestWithParams();
// 无参数 POST 请求
sendPostRequestNoParams();
// 带参数 POST 请求,使用 User 对象
sendPostRequestWithUser();
}

// 无参数 GET 请求示例
public static void sendGetRequestNoParams() throws Exception {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("http://localhost:8080/posts/1");
HttpResponse response = httpClient.execute(request);
System.out.println("GET No Params Response Status: "
+ response.getStatusLine().getStatusCode());
System.out.println("Response Content: " + EntityUtils.toString(response.getEntity()));
}
}

// 带参数 GET 请求示例
public static void sendGetRequestWithParams() throws Exception {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
URI uri = new URIBuilder("http://localhost:8080/posts")
.addParameter("userId", "1")
.build();
HttpGet request = new HttpGet(uri);
HttpResponse response = httpClient.execute(request);
System.out.println("GET With Params Response Status: "
+ response.getStatusLine().getStatusCode());
System.out.println("Response Content: " + EntityUtils.toString(response.getEntity()));
}
}

// 无参数 POST 请求示例
public static void sendPostRequestNoParams() throws Exception {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost request = new HttpPost("http://localhost:8080/posts");
HttpResponse response = httpClient.execute(request);
System.out.println("POST No Params Response Status: "
+ response.getStatusLine().getStatusCode());
System.out.println("Response Content: " + EntityUtils.toString(response.getEntity()));
}
}

// 带 User 对象参数的 POST 请求示例
public static void sendPostRequestWithUser() throws Exception {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost request = new HttpPost("http://localhost:8080/posts");

// 创建 User 对象
User user = new User("Alice", 25);

// 将 User 对象转换为 JSON 字符串
ObjectMapper objectMapper = new ObjectMapper();
String jsonPayload = objectMapper.writeValueAsString(user);

// 设置 JSON 请求体
StringEntity entity = new StringEntity(jsonPayload);
entity.setContentType("application/json"); // 标记格式为application/json,http才知道它是json
request.setEntity(entity);

HttpResponse response = httpClient.execute(request);

System.out.println("POST With User Object Response Status: "
+ response.getStatusLine().getStatusCode());
System.out.println("Response Content: " + EntityUtils.toString(response.getEntity()));
}
}
}

// 定义 User 类
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private String name;
private int age;

}

RestTemplate

简介

Spring 提供了一个 RestTemplate 模板工具类,对基于 Http 的客户端进行了封装,并且实现了对象与 JSON 的序列化与反序列化,非常方便(上面的HttpClient返回的相应是 JSON 格式的)。RestTemplate 并没有限定 Http 客户端类型,而是进行了抽象,目前常用的三种都支持:

  • HttpClient
  • OKHttp
  • JDK原生的URLConnection(默认的)

RestTemplate是阻塞式的,性能没有响应式式的 WebClient 优秀

具体使用

  1. 引入Maven依赖
1
2
3
4
5
<!-- Spring Web 依赖,包含 RestTemplate 和其他 Web 相关功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 创建配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());
requestFactory.setConnectTimeout(5000); // 设置连接超时时间,单位为毫秒
requestFactory.setReadTimeout(10000); // 设置读取超时时间,单位为毫秒

return builder
.requestFactory(() -> requestFactory)
.build();
}

}
  1. 具体使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class RestTemplateService {

@Autowired
private RestTemplate restTemplate;

// GET 请求示例
public String getForObjectExample() {
String url = "https://jsonplaceholder.typicode.com/posts/1";
String response = restTemplate.getForObject(url, String.class);
return response; // 返回 JSON 格式的响应
}

// POST 请求示例
public String postForObjectExample() {
String url = "https://jsonplaceholder.typicode.com/posts";

// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("title", "foo");
requestBody.put("body", "bar");
requestBody.put("userId", 1);

// 发送 POST 请求并接收响应
String response = restTemplate.postForObject(url, requestBody, String.class);
return response;
}
}
  1. 常用方法
方法名请求类型描述
getForObjectGET获取资源并将响应直接映射为 Java 对象。
getForEntityGET获取资源,返回 ResponseEntity,包含状态码、头信息和响应体。
postForObjectPOST发送请求体,并将响应映射为 Java 对象。
postForEntityPOST发送请求体,返回 ResponseEntity
putPUT发送 PUT 请求,无返回值。
deleteDELETE发送 DELETE 请求,无返回值。
exchange多种提供更灵活的方式发送请求(支持自定义 HTTP 方法、头信息等)。
execute多种最底层的 HTTP 操作,可完全自定义请求。

  1. 使用 exchange 发送自定义请求

如果需要添加自定义请求头或使用复杂配置,可以使用 exchange 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

public class RestTemplateCustomExample {

public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "https://jsonplaceholder.typicode.com/posts";

// 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

// 构建请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("title", "foo");
requestBody.put("body", "bar");
requestBody.put("userId", 1);

// 构建 HttpEntity
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);

// 发送 POST 请求
ResponseEntity<String> responseEntity = restTemplate.exchange(
url,
HttpMethod.POST,
requestEntity,
String.class
);

// 打印响应
System.out.println("Response Status: " + responseEntity.getStatusCode());
System.out.println("Response Body: " + responseEntity.getBody());
}
}

HttpClient及其连接池使用

1. 功能模块

  1. HttpClient 接口和实现
    • HttpClient:这是 Apache HttpClient 的核心接口,定义了执行 HTTP 请求的方法
    • CloseableHttpClient:HttpClient 的主要实现类,提供了对 HTTP 请求的同步执行,并实现了 Closeable 接口,以便在使用完毕后释放资源。
  2. 请求与相应
    • 请求类:包括 HttpGetHttpPostHttpPutHttpDelete 等,分别用于不同类型的 HTTP 请求。
    • 相应类HttpResponse 接口用于表示 HTTP 响应,提供方法获取状态码、响应头和响应体。
    • HttpEntity:表示请求或响应的内容主体,可以是流、字符串、字节数组等。【具体看 entity 接口的实现类】
  3. 连接管理
    • HttpClientConnectionManager:接口用于管理 HTTP 连接的生命周期。
    • PoolingHttpClientConnectionManager:常用的实现类,支持连接池管理,允许连接复用以提高性能,可以配置最大连接数和每个路由的最大连接数。
  4. 请求配置
    • RequestConfig:用于配置请求参数,如连接超时、套接字超时、代理设置、重定向策略等。
    • SocketConfig:用于配置套接字参数,如 TCP_NODELAY 、SO_TIMEOUT 等。
  5. 身份认证
    • CredentialsProvider:用于提供认证信息,支持多种认证机制(Basic、Digest、NTLM、Kerberos)。
    • AuthCache:缓存认证信息,减少重复认证的开销。
  6. 重试和重定向
    • HttpRequestRetryHandler:定义重试策略,处理请求失败后的重试逻辑。
    • RedirectStrategy:管理请求重定向,处理 3xx 响应状态码。
  7. 异步处理
    • HttpAsyncClient:提供异步 HTTP 请求的支持,通过回调接口处理异步请求的结果。
  8. 拦截器
    • HttpRequestInterceptorHttpResponseInterceptor:允许在请求发送前和响应处理前后执行自定义逻辑,提供请求和响应的拦截和修改功能。
  9. Cookie 管理
    • CookieStore:用于存储和管理 HTTP Cookie。
    • CookieSpec:定义 Cookie 的处理规则。

2. 工作原理

  1. 连接管理器

    • PoolingHTTPClientConnectionManager:用于管理HTTP连接池。通过设置最大连接数和每个路由的最大连接数,确保连接的高效复用。
  2. 请求配置

    • 使用 RequestConfig 配置请求的超时时间、包括套接字超时、连接超时和请求超时。这样可以确保在网络条件不佳时,程序不会无限制地等待。

      • 套接字超时:数据传输的最大等待时间

      • 连接超时:连接建立的最大等待时间

      • 请求超时:整体请求的最大等待时间

  3. HttpClient 实例化

    • 使用 HttpClients.custom() 方法创建 CloseableHttpClient 实例,并应用连接管理器和请求配置。
    • CloseableHttpClientHttpClient 的主要实现类,支持资源管理。
  4. 创建请求

    • 创建一个 HttpGet 实例,指定请求的 URL 。在实际应用中,可以根据需要使用 HttpPostHttpPut 等请求类。
  5. 执行请求

    • 使用 httpClient.execute(httpGet) 方法执行请求,并获取 CloseableHttpResponse 对象。
    • 连接管理器负责分配和管理连接的生命周期。
  6. 处理响应

    • HttpResponse 对象中获取状态行,检查请求是否成功。
    • 使用 EntityUtils.toString(entity) 将响应实体转换为字符串,方便读取和处理响应数据。
  7. 资源释放

    • 使用 try-with-resources 语法,确保 CloseableHttpClientCloseableHttpResponse 在使用完毕后自动关闭,释放系统资源。

为了更好地理解 Apache HttpClient 的工作原理,下面将通过一个简单的示例代码来演示其核心组件的使用和工作流程。这段代码将展示如何创建一个 HTTP 客户端,发送 GET 请求,并处理响应。

  • 引入Maven依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version> <!-- 请使用最新的稳定版本 -->
</dependency>
  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class HttpClientExample {
public static void main(String[] args) {
// 创建一个连接管理器
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(100); // 设置最大连接数
connManager.setDefaultMaxPerRoute(20); // 每个路由的默认最大连接数

// 创建一个请求配置
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(5000) // 套接字超时
.setConnectTimeout(5000) // 连接超时
.setConnectionRequestTimeout(5000) // 请求超时
.build();

// 创建一个可关闭的 HttpClient 实例,并应用连接管理器和请求配置
try (CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build()) {

// 创建一个 GET 请求
HttpGet httpGet = new HttpGet("https://www.example.com");

// 执行请求
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
// 获取响应状态
System.out.println("Response Status: " + response.getStatusLine());

// 获取响应实体
HttpEntity entity = response.getEntity();
if (entity != null) {
// 将响应实体转换为字符串
String responseBody = EntityUtils.toString(entity);
System.out.println("Response Body: " + responseBody);
}
}

} catch (IOException e) {
e.printStackTrace();
}
}
}

3. 异步请求

Apache HttpClient 的异步请求功能是通过 HttpAsyncClient 实现的,它使用 Java NIO 的非阻塞 I/O 来处理 HTTP 请求。它可以在不阻塞线程的情况下发送和接收请求,满足高并发的需求。

3.1 工作原理

  1. 非阻塞 I/O
    • Apache HttpAsyncClient 使用 Java NIO 来实现非阻塞 I/O。请求和响应的处理可以在同一线程中进行,不需要为每一个请求单独分配一个独立的线程。
  2. 事件驱动
    • 通过事件驱动机制处理 I/O 操作的完成。请求的完成、失败或取消会触发响应的回调方法。
  3. 回调机制
    • 使用 FutureCallback 接口处理请求的不同结果,包括成功、失败和取消。

3.2 调优配置

为了在高并发环境下实现最佳性能,配置连接管理和线程池是关键。

  1. 连接管理器
    • 使用 PollingHttpClientConnectionManager 管理连接池,可以设置最大连接数和每个路由的最大连接数。
  2. IO Reactor 配置
    • 配置 IO 线程的数量,通常设置为可用处理器的数量,以充分利用多核 CPU 的性能。
  3. 线程池
    • 自定义线程池可以通过 ExecutorServiceThreadFactory 来控制异步请求的执行线程数量。
连接池

PoolingHttpClientConnectionManager 管理连接池,支持连接复用和高效的多线程环境,具体见下文。

IO Reactor 配置

IO Reactor 是 Apache HttpClient 异步客户端的核心组件之一,主要用于处理网络 I/O 操作。它的主要职责包括:

  • 事件监听
    • IO Reactor 监听网络事件(如连接建立、数据到达等),并触发相应的处理操作。
    • 使用 Java NIO 的 Selector 机制来实现高效的事件驱动。
  • 线程管理
    • IO Reactor 通常包含一组 IO 线程,这些线程专门用于处理网络 I/O 操作。
    • 这些线程的数量通常设置为可用处理器的数量,以充分利用多核 CPU 的性能
自定义线程池

线程池主要用于管理异步请求的执行线程,它负责:

  • 任务调度
    • 线程池负责调度和执行异步请求的任务。
    • 当一个异步请求被提交到 HttpClient 时,线程池会选择一个线程来执行该请求。
  • 资源管理
    • 线程池可以限制执行请求的线程数量,防止过多的线程导致系统资源耗尽。
    • 通过设置线程的大小,可以人为控制并发请求的数量。

示例代码

  • 引入Maven依赖
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.5</version> <!-- 请使用最新的稳定版本 -->
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.4.15</version> <!-- 请使用最新的稳定版本 -->
</dependency>
  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class AsyncHttpClientExample {
public static void main(String[] args) throws Exception {
// 配置 IO Reactor
IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setIoThreadCount(Runtime.getRuntime().availableProcessors()) // 设置IO线程数
.build();

// 创建连接管理器
PoolingNHttpClientConnectionManager connectionManager = new PoolingNHttpClientConnectionManager(
new DefaultConnectingIOReactor(ioReactorConfig));

connectionManager.setMaxTotal(100); // 设置最大连接数
connectionManager.setDefaultMaxPerRoute(20); // 每个路由的默认最大连接数

// 自定义线程池
ThreadFactory threadFactory = Executors.defaultThreadFactory();
ExecutorService executorService = Executors.newFixedThreadPool(10, threadFactory);

// 创建异步 HttpClient
try (CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
.setConnectionManager(connectionManager)
.setThreadFactory(threadFactory)
.setExecutorService(executorService)
.build()) {
httpClient.start();

// 使用 CountDownLatch 等待请求完成
CountDownLatch latch = new CountDownLatch(1);

// 创建 GET 请求
HttpGet request = new HttpGet("https://www.example.com");

// 执行请求并处理回调
httpClient.execute(request, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response) {
// 请求成功时调用
System.out.println("Response: " + response.getStatusLine());
latch.countDown(); // 减少计数器
}

@Override
public void failed(Exception ex) {
// 请求失败时调用
System.out.println("Request failed: " + ex.getMessage());
latch.countDown(); // 减少计数器
}

@Override
public void cancelled() {
// 请求取消时调用
System.out.println("Request cancelled");
latch.countDown(); // 减少计数器
}
});

// 等待请求完成
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}

4. 连接池

4.1 基本概念

Apache HttpClient 的连接池通过 PoolingHttpClientConnectionManager 提供高效的连接管理功能。它允许客户端在执行 HTTP 请求时重用连接,以提高性能和资源利用率。

4.2 基本原理

  1. 连接复用
    • 连接池通过维护一个连接集合,允许多个请求重用同一连接,避免每次请求都需要建立和关闭连接的开销。
  2. 连接分配
    • 当一个请求需要发送是,连接池首先检查是否有空闲连接可用。如果有,则直接复用;如果没有,则根据配置创建新的连接。
  3. 连接释放
    • 请求完成后,连接并不立即关闭,而是被释放回池中,以便后续请求使用。
  4. 连接过期和验证
    • 为了避免使用失效的连接,连接池会在连接空闲时间过长时对其进行验证,并根据配置自动清理过期或不再有效的连接。

4.3 关键组件和配置

  1. PoolingHttpClientConnectionManager

    • 核心连接管理器,负责维护连接池。
    • 提供方法设置总连接数(setMaxTotal)和每个路由的最大连接数(setDefaultMaxPerRoute)。
  2. 路由(Route)

    • 由目标主机、端口和协议组成,用于识别不同的连接路径。
    • 可以为特定路由设置不同的最大连接数。
  3. 连接保持策略(Keep-Alive Strategy)

    • 通过 ConnectionKeepAliveStrategy 确定连接的保持时间。
    • 可以自定义策略,以根据响应头信息动态调整连接的保持时间。
  4. 连接验证和清理

    • 使用 IdleConnectionEvictor 定期检查和清理空闲连接,防止资源浪费。
    • validateAfterInactivity 设置连接空闲时间超过某个阈值后,在使用前需要进行验证。

4.4 工作机制

  1. 请求到达
    • 客户端请求到达,连接管理器检查是否有可用的空闲连接。
    • 如果有,则复用空闲连接;否则,根据配置创建新连接。
  2. 连接使用
    • 请求使用连接和服务器进行通信。
    • 完成后,连接被标记为可用,并返回连接池。
  3. 连接验证
    • 在空闲连接重新使用前,连接管理器可能会对其进行验证,以确保连接仍然有效。
  4. 连接清理
    • 定期清理空闲和过期连接,以释放系统资源。

4.5 示例代码

以下是一个示例代码,展示如何配置和使用 Apache HttpClient 的连接池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class HttpClientConnectionPoolExample {
public static void main(String[] args) {
// 创建连接池管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(50); // 设置连接池的总最大连接数
connectionManager.setDefaultMaxPerRoute(5); // 设置每个路由的默认最大连接数

// 为特定的主机设置最大连接数
HttpHost targetHost = new HttpHost("www.example.com", 80);
connectionManager.setMaxPerRoute(new HttpRoute(targetHost), 10);

// 设置自定义的 ConnectionKeepAliveStrategy
ConnectionKeepAliveStrategy keepAliveStrategy = (HttpResponse response, HttpContext context) -> {
// 从响应头中获取 Keep-Alive 信息
long keepAliveDuration = 5 * 1000; // 默认 5 秒
return keepAliveDuration;
};

// 设置连接超时时间和读取超时时间
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) // 连接超时时间:5秒
.setSocketTimeout(5000) // 读取超时时间:5秒
.build();

// 创建 HttpClient 并配置连接管理器、请求配置和保持策略
try (CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.setKeepAliveStrategy(keepAliveStrategy)
.build()) {

// 创建一个 GET 请求
HttpGet request = new HttpGet("http://www.example.com");

// 执行请求
try (CloseableHttpResponse response = httpClient.execute(request)) {
System.out.println("Response Status: " + response.getStatusLine());

// 获取响应实体
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println("Response Body: " + responseBody);
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

4.6 作用范围

OkHttp 不同,Apache HttpClient 的连接池不仅可以控制总最大连接数(setMaxTotal),还可以控制每个路由(每个目标主机)的最大连接数(setDefaultMaxPerRoute),甚至还可以为特定的主机或路由指定不同的最大连接数(setMaxPerRoute)。

虽然 OkHttp 控制不了单机的最大连接数,但是有调度器Dispatcher)替代,可以实现单机最大并发请求数控制。而Apache HttpClient中则没有类似调度器Dispatcher)的组件。所以二者各有互补。

5. 和 OkHttp 对比

从性能角度来看,OkHttp 和 Apache HttpClient 各有其优劣,具体取决于使用场景和配置。以下是对两者在性能方面的详细对比:

5.1. 连接管理和复用

  • OkHttp

    • 连接池:OkHttp 内置了一个高效的连接池,自动管理连接的生命周期,默认情况下支持 HTTP/2 的多路复用,能够显著减少网络延迟。

    • 连接复用:通过复用连接来减少连接建立的开销,尤其是在高并发场景下,表现出色。

    • 并发性能:得益于 HTTP/2 支持和连接池管理,OkHttp 在处理并发请求时具有较高的吞吐量和低延迟。

  • Apache HttpClient

    • 连接池管理:使用 PoolingHttpClientConnectionManager 进行连接池管理,允许更细粒度的连接配置。

    • 连接复用:也支持连接复用,但需要手动配置和管理连接池,使用不当可能导致性能瓶颈。

    • 路由控制:提供对连接路由的详细控制,适合复杂的网络拓扑,但可能增加配置复杂性。

5.2. 协议支持

  • OkHttp

    • HTTP/2 支持:OkHttp 原生支持 HTTP/2,能够有效提高并发请求的性能,特别是在长连接和多请求场景下。

    • WebSocket 支持:内置支持 WebSocket,可以用于实时通信场景。

  • Apache HttpClient

    • HTTP/1.1 支持:主要支持 HTTP/1.1,缺乏对 HTTP/2 的原生支持,可能在现代网络应用中表现稍逊。

    • 扩展性:虽然可以通过第三方库实现 HTTP/2 支持,但增加了复杂性和潜在的性能开销。

5.3. 异步处理

  • OkHttp

    • 异步请求:OkHttp 提供了简单易用的异步 API,通过回调机制处理请求和响应,减少了线程管理的开销。

    • 线程池管理:内置的线程池管理简化了异步请求的调度,提高了异步处理的效率。

  • Apache HttpClient

    • 异步请求:通过 HttpAsyncClient 提供异步请求支持,但配置和使用相对复杂,需要手动管理线程池。

    • 复杂场景:在复杂的异步场景中可能需要更多的样板代码和配置,增加了开发和维护成本。

5.4. 缓存机制

  • OkHttp

    • 内置缓存:OkHttp 提供了内置的缓存机制,可以自动处理 HTTP 缓存头,减少网络请求次数,提升性能。

    • 灵活的缓存策略:支持自定义缓存策略,通过拦截器机制实现缓存控制。

  • Apache HttpClient

    • 缓存支持:需要手动实现缓存机制,Apache HttpClient 不提供开箱即用的缓存支持,增加了实现复杂性。

5.5. 资源消耗

  • OkHttp

    • 轻量级:相对较小的内存和 CPU 占用,适合在资源受限的环境(如 Android)中使用。

    • 优化的资源使用:得益于高效的连接管理和异步处理机制,OkHttp 在资源使用上表现优秀。

  • Apache HttpClient

    • 资源消耗:由于其功能丰富和高度可配置,可能在复杂配置下增加内存和 CPU 占用。

    • 线程安全:设计为线程安全,但需要合理配置以避免资源浪费。

5.6. 总结

  • OkHttp
    • 在现代应用中,尤其是需要 HTTP/2 和 WebSocket 支持的场景下,OkHttp 在性能上表现优异。
    • 适合需要高并发处理和低延迟的应用场景,如移动应用和微服务架构。
    • 内置的缓存和异步支持简化了开发,减少了资源消耗。
  • Apache HttpClient
    • 在需要复杂配置和扩展的场景下,Apache HttpClient 提供了灵活性,但可能在性能上稍逊。
    • 适合在服务器端处理复杂的 HTTP 请求,尤其是需要复杂身份验证和路由控制的场景。
    • 需要手动优化连接管理和异步处理以提升性能。

HttpClient工具类模板

HttpClientUtil 类概述

该类是一个 HTTP 请求工具类,包含了发送 GETPOST 请求的功能,支持普通的表单数据和 JSON 数据的发送。它使用 Apache HttpClient 来执行 HTTP 请求,并通过配置连接超时、读取超时等参数来增强请求的稳定性。


代码注释与功能说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* HttpClient工具类,提供了发送GET、POST请求的方法
*/
public class HttpClientUtil {

// 超时时间(5秒)
static final int TIMEOUT_MSEC = 5 * 1000;

/**
* 发送GET方式请求
*
* @param url 请求的URL
* @param paramMap 请求的参数,键值对形式
* @return 返回响应结果字符串
*/
public static String doGet(String url, Map<String, String> paramMap) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();

String result = "";
CloseableHttpResponse response = null;

try {
// 构造URL并附加参数
URIBuilder builder = new URIBuilder(url);
if (paramMap != null) {
for (String key : paramMap.keySet()) {
builder.addParameter(key, paramMap.get(key)); // 添加参数
}
}
URI uri = builder.build();

// 创建GET请求
HttpGet httpGet = new HttpGet(uri);

// 发送请求
response = httpClient.execute(httpGet);

// 判断响应状态
if (response.getStatusLine().getStatusCode() == 200) {
result = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保释放资源
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return result;
}

/**
* 发送POST方式请求(表单数据)
*
* @param url 请求的URL
* @param paramMap 请求的参数,键值对形式
* @return 返回响应结果字符串
* @throws IOException
*/
public static String doPost(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";

try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);

// 创建参数列表
if (paramMap != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
// 将参数封装为表单数据
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity); // 设置表单数据
}

httpPost.setConfig(builderRequestConfig()); // 设置请求配置

// 执行HTTP请求
response = httpClient.execute(httpPost);

// 获取响应内容
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
// 关闭响应流
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return resultString;
}

/**
* 发送POST方式请求(JSON数据)
*
* @param url 请求的URL
* @param paramMap 请求的参数,键值对形式
* @return 返回响应结果字符串
* @throws IOException
*/
public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";

try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);

if (paramMap != null) {
// 构造JSON格式数据
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
jsonObject.put(param.getKey(), param.getValue());
}
StringEntity entity = new StringEntity(jsonObject.toString(), "utf-8");
// 设置请求内容
entity.setContentEncoding("utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
}

httpPost.setConfig(builderRequestConfig()); // 设置请求配置

// 执行HTTP请求
response = httpClient.execute(httpPost);

// 获取响应内容
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
// 关闭响应流
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return resultString;
}

/**
* 构建请求配置,设置连接超时和读取超时
*
* @return 返回配置对象
*/
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC) // 设置连接超时
.setConnectionRequestTimeout(TIMEOUT_MSEC) // 设置获取连接超时
.setSocketTimeout(TIMEOUT_MSEC) // 设置读取数据超时
.build();
}

}

使用方法

  1. 发送 GET 请求
1
2
3
4
5
6
7
8
9
// 定义请求 URL 和参数
String url = "https://www.example.com/api";
Map<String, String> params = new HashMap<>();
params.put("param1", "value1");
params.put("param2", "value2");

// 调用 doGet 方法发送请求
String response = HttpClientUtil.doGet(url, params);
System.out.println(response);
  1. 发送 POST 请求(表单数据)
1
2
3
4
5
6
7
8
9
// 定义请求 URL 和参数
String url = "https://www.example.com/api";
Map<String, String> params = new HashMap<>();
params.put("param1", "value1");
params.put("param2", "value2");

// 调用 doPost 方法发送 POST 请求
String response = HttpClientUtil.doPost(url, params);
System.out.println(response);
  1. 发送 POST 请求(JSON 数据)
1
2
3
4
5
6
7
8
9
// 定义请求 URL 和参数
String url = "https://www.example.com/api";
Map<String, String> params = new HashMap<>();
params.put("param1", "value1");
params.put("param2", "value2");

// 调用 doPost4Json 方法发送 JSON 格式的 POST 请求
String response = HttpClientUtil.doPost4Json(url, params);
System.out.println(response);

总结

  • doGet 方法用于发送 GET 请求,支持 URL 参数传递。
  • doPost 方法用于发送 POST 请求,支持表单数据。
  • doPost4Json 方法用于发送 POST 请求,支持 JSON 数据格式。

这些方法统一返回请求的响应内容(字符串形式)。使用时,只需要调用相应的方法并传入 URL 和参数即可。

  • Title: HttpClient
  • Author: Lu
  • Created at : 2024-07-18 16:42:10
  • Updated at : 2024-07-18 18:23:54
  • Link: https://lusy.ink/2024/07/18/HttpClient/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments