1

最常见的 Http 客户端库除了 OkHttp,就是今天要介绍的 Apache HttpClient 了。

Apache HttpClient 是 Apache Software Foundation 旗下的一个开源项目,旨在为 Java 应用程序提供高效、可靠的 HTTP 客户端功能。它是 Apache HttpComponents 项目的一部分,该项目专注于创建和维护 HTTP 协议的组件。

Apache HttpClient 是一个成熟的、广泛使用的 HTTP 客户端库,用于处理 HTTP 协议的通信。它提供了丰富的功能和高度的可配置性,适用于各种复杂的网络通信场景。以下是对 Apache HttpClient 及其功能模块的详细介绍。

1. 功能模块

  1. HttpClient 接口和实现

    • HttpClient:这是 Apache HttpClient 的核心接口,定义了执行 HTTP 请求的方法。
    • CloseableHttpClientHttpClient 的主要实现类,提供了对 HTTP 请求的同步执行,并实现了 Closeable 接口,以便在使用完毕后释放资源。
  2. 请求与响应

    • 请求类:包括 HttpGetHttpPostHttpPutHttpDelete 等,分别用于不同类型的 HTTP 请求。
    • 响应类HttpResponse 接口用于表示 HTTP 响应,提供方法获取状态码、响应头和响应体。
    • HttpEntity:表示请求或响应的内容主体,可以是流、字符串、字节数组等。
  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. 工作原理
    1. 连接管理器
    2. PoolingHttpClientConnectionManager 用于管理 HTTP 连接池。通过设置最大连接数和每个路由的最大连接数,确保连接的高效复用。
  1. 请求配置

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

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

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

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

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

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

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

2. maven依赖

以下是使用 Apache HttpClient 的基本依赖项:

HttpClient 这是核心库,用于发送 HTTP 请求和接收响应。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.14</version> <!-- 请使用最新的稳定版本 -->
</dependency>

HttpCore 提供了低级别的 HTTP 传输和协议处理功能,HttpClient 依赖于它。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.15</version> <!-- 请使用最新的稳定版本 -->
</dependency>
3. 示例代码
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.io.IOException;

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. 连接管理器

    • 使用 PoolingNHttpClientConnectionManager 管理连接池,可以设置最大连接数和每个路由的最大连接数。
  2. IO Reactor 配置

    • 配置 IO 线程的数量,通常设置为可用处理器的数量,以充分利用多核 CPU 的性能。
  3. 线程池

    • 自定义线程池可以通过 ExecutorServiceThreadFactory 来控制异步请求的执行线程数量。

连接管理器后面单独讲解,先讲解其他的。

1. IO Reactor 配置

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

  1. 事件监听

    • IO Reactor 监听网络事件(如连接建立、数据到达等),并触发相应的处理操作。
    • 使用 Java NIO 的 Selector 机制来实现高效的事件驱动。
  2. 线程管理

    • IO Reactor 通常包含一组 IO 线程,这些线程专门用于处理网络 I/O 操作。
    • 这些线程的数量通常设置为可用处理器的数量,以充分利用多核 CPU 的性能。
2. 自定义线程池

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

  1. 任务调度

    • 线程池负责调度和执行异步请求的任务。
    • 当一个异步请求被提交到 HttpClient 时,线程池会选择一个线程来执行该请求。
  2. 资源管理

    • 线程池可以限制执行请求的线程数量,防止过多的线程导致系统资源耗尽。
    • 通过设置线程池的大小,可��控制并发请求的数量。

3.3. 示例代码

Maven 依赖

在使用 Apache HttpAsyncClient 之前,需要在 pom.xml 中额外添加以下依赖:

<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>
示例代码

以下是一个使用 Apache HttpClient 进行异步请求的完整示例:

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;

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 请求时重用连接,以提高性能和资源利用率。以下是其工作原理的详细介绍,以及一个代码示例。

1. 连接池的基本原理
  1. 连接复用:

    • 连接池通过维护一个连接集合,允许多个请求重用同一连接,避免每次请求都需要建立和关闭连接的开销。
  2. 连接分配:

    • 当一个请求需要发送时,连接池首先检查是否有空闲连接可用。如果有,则直接复用;如果没有,则根据配置创建新的连接。
  3. 连接释放:

    • 请求完成后,连接并不立即关闭,而是被释放回池中,以供后续请求使用。
  4. 连接过期和验证:

    • 为了避免使用失效的连接,连接池会在连接空闲时间过长时对其进行验证,并根据配置自动清理过期或不再有效的连接。
2. 关键组件和配置
  1. PoolingHttpClientConnectionManager:

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

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

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

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

4.2. 工作机制

  1. 请求到达:

    • 客户端请求到达,连接管理器检查是否有可用的空闲连接。
    • 如果有,则复用空闲连接;否则,根据配置创建新连接。
  2. 连接使用:

    • 请求使用连接与服务器进行通信。
    • 完成后,连接被标记为可用,并返回连接池。
  3. 连接验证:

    • 在空闲连接重新使用前,连接管理器可能会对其进行验证,以确保连接仍然有效。
  4. 连接清理:

    • 定期清理空闲和过期连接,以释放系统资源。
代码示例

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

import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
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.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.protocol.HttpContext;
import org.apache.http.HttpResponse;

import java.io.IOException;

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.3. 作用范围

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 请求,尤其是需要复杂身份验证和路由控制的场景。
    • 需要手动优化连接管理和异步处理以提升性能。

KerryWu
641 声望159 粉丝

保持饥饿


引用和评论

0 条评论