Apache HttpClient

0 阅读7分钟

一,简介

Apache HttpClient 是 Java 中的一个强大且广泛使用的 HTTP 客户端库,用于发送 HTTP 请求(如 GET、POST 等)并与 HTTP 服务器进行交互。它是 Apache Software Foundation 下的开源项目,属于 HttpComponents 项目的一部分。

主要特性

  1. 支持 HTTP 协议
    • 完整支持 HTTP/1.1 和 HTTP/2,兼容 RFC 标准。
    • 提供同步(阻塞)和异步(非阻塞)的请求方式。
  2. 连接管理
    • 内置连接池(PoolingHttpClientConnectionManager),复用连接提升性能。
    • 支持多线程环境下的并发请求。
  3. 灵活的 API
    • 可定制请求头、参数、超时时间、代理等。
    • 支持 HTTPS、Cookie、重定向、身份认证(Basic、Digest、NTLM 等)。
  4. 扩展性
    • 支持拦截器(Interceptor),可在请求/响应前后插入自定义逻辑。
    • 可集成 JSON/XML 解析库(如 Jackson、Gson)。

核心组件

  1. HttpClient 发送请求的核心接口,通常通过 HttpClientBuilder 创建。
  2. HttpRequest 表示 HTTP 请求(如 HttpGetHttpPost)。
  3. HttpResponse 封装服务器返回的响应(状态码、响应体、头信息等)。
  4. EntityUtils 工具类,用于处理响应实体(如将响应体转为字符串)。

二,快速入门

  1. 添加依赖

    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3.1</version>
    </dependency>
    
  2. 发送 GET 请求

 ```java
 import org.apache.hc.client5.http.classic.methods.HttpGet;
 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.core5.http.io.entity.EntityUtils;
 
 public class QuickStart {
     public static void main(String[] args) throws Exception {
         // 1. 创建 HttpClient 实例
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             // 2. 创建 GET 请求
             HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
             
             // 3. 发送请求并获取响应
             try (CloseableHttpResponse response = httpClient.execute(request)) {
                 // 4. 解析响应内容
                 String responseBody = EntityUtils.toString(response.getEntity());
                 System.out.println("Response: " + responseBody);
                 
                 // 5. 获取状态码
                 System.out.println("Status Code: " + response.getCode());
             }
         }
     }
 }
 ```

3. 发送 POST 请求(JSON 数据)

 ```java
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.entity.EntityBuilder;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 
 public class PostExample {
     public static void main(String[] args) throws Exception {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             HttpPost request = new HttpPost("https://jsonplaceholder.typicode.com/posts");
             
             // 设置 JSON 请求体
             String jsonBody = "{\"title\":\"foo\", \"body\":\"bar\", \"userId\":1}";
             request.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
             
             // 发送请求
             try (CloseableHttpResponse response = httpClient.execute(request)) {
                 System.out.println(EntityUtils.toString(response.getEntity()));
                 System.out.println("Status Code: " + response.getCode());
             }
         }
     }
 }
 ```

4. 发送Put请求

 ```java
 import org.apache.hc.client5.http.classic.methods.HttpPut;
 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.core5.http.ContentType;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 
 public class PutRequestExample {
     public static void main(String[] args) throws Exception {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             // 1. 创建 PUT 请求
             HttpPut putRequest = new HttpPut("https://jsonplaceholder.typicode.com/posts/1");
             
             // 2. 设置 JSON 请求体
             String jsonBody = "{\"title\":\"Updated Title\", \"body\":\"New content\", \"userId\":1}";
             putRequest.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
             
             // 3. 可选:设置请求头
             putRequest.setHeader("Accept", "application/json");
             
             // 4. 发送请求并处理响应
             try (CloseableHttpResponse response = httpClient.execute(putRequest)) {
                 System.out.println("状态码: " + response.getCode());
                 System.out.println("响应体: " + EntityUtils.toString(response.getEntity()));
             }
         }
     }
 }

5. 发送DELETE请求

 ```java
 import org.apache.hc.client5.http.classic.methods.HttpDelete;
 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.core5.http.io.entity.EntityUtils;
 
 public class DeleteRequestExample {
     public static void main(String[] args) throws Exception {
         try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
             // 1. 创建 DELETE 请求
             HttpDelete deleteRequest = new HttpDelete("https://jsonplaceholder.typicode.com/posts/1");
             
             // 2. 可选:设置请求头
             deleteRequest.setHeader("Authorization", "Bearer token123");
             
             // 3. 发送请求并处理响应
             try (CloseableHttpResponse response = httpClient.execute(deleteRequest)) {
                 System.out.println("状态码: " + response.getCode());
                 if (response.getEntity() != null) {
                     System.out.println("响应体: " + EntityUtils.toString(response.getEntity()));
                 } else {
                     System.out.println("资源已删除,无响应体");
                 }
             }
         }
     }
 }

三,扩展功能

3.1 设置超时时间

超时时间的设置在HttpClient中非常重要,可以防止请求因为网络问题或服务器响应过慢而无限期地挂起。超时时间主要分为两种

  • 连接超时时间(Connection Timeout)

    连接超时时间指的是建立连接所需的最大时间。如果在这段时间内没有建立连接,连接将被放弃。

  • 读取超时时间(Socket Timeout)

    读取超时时间指的是从服务器读取数据的最大时间。如果在这段时间内没有读取到数据,读取操作将被放弃。

    @Test
    public void testTimeout() {
        // 创建请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(10) // 连接超时时间
                .setSocketTimeout(30)   //读取超时时间
                .build();

        // 创建HttpClient实例并应用配置
        CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

3.2 添加自定义请求头

通过HttpRequest或其子类(如HttpGetHttpPost等)的setHeader方法来设置自定义请求头。

    @Test
    public void testCustomHeader(){
        // 创建HttpClient实例
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建HttpGet请求
        HttpGet getRequest = new HttpGet("http://example.com/api");
        // 添加自定义请求头
        getRequest.setHeader("Authorization", "Bearer your_token_here");
        getRequest.setHeader("Custom-Header", "CustomValue");
    }

3.3 使用连接池

在HttpClient中,连接池是用于管理HTTP连接的机制,它可以显著提高网络通信的性能和效率。HttpClient 4.x 版本使用 PoolingHttpClientConnectionManager 来实现连接池。

    @Test
    public void testClientPool(){
        // 创建连接池管理器
        try(PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager()){
            connectionManager.setMaxTotal(200); // 设置最大连接数
            connectionManager.setDefaultMaxPerRoute(20); // 设置每个路由的最大连接数
            connectionManager.setValidateAfterInactivity(30);//设置连接在空闲多长时间后进行验证
            // 创建请求配置
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(5000) // 连接超时时间,单位:毫秒
                    .setSocketTimeout(10000) // 套接字超时时间(读取超时),单位:毫秒
                    .setConnectionRequestTimeout(3000) // 从连接池获取连接超时,单位:毫秒
                    .build();

            // 创建HttpClient
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(requestConfig)
                    .build();
            // 使用httpClient执行请求
            // ...
        }
    }

配置说明

  • setConnectTimeout(int timeout):设置连接超时时间,即连接建立的超时时间。超时时间到达之前如果无法建立连接,则会抛出 SocketTimeoutException
  • setSocketTimeout(int timeout):设置套接字超时时间,即数据读取的超时时间。超时时间到达之前如果没有接收到数据,则会抛出 SocketTimeoutException
  • setConnectionRequestTimeout(int timeout):设置从连接池获取连接的超时时间。如果超时时间到达之前无法从连接池获取连接,则会抛出 ConnectionPoolTimeoutException
  • setValidateAfterInactivity(long ms):设置连接在空闲多长时间后进行验证(检查连接是否仍然有效)。

3.4 请求重试策略

在 HttpClient 4.x 中,重试机制可以通过实现 HttpRequestRetryHandler 接口来进行定制。这个接口允许你定义重试逻辑,以决定在请求失败时是否以及如何进行重试。

  1. 实现 HttpRequestRetryHandler 接口

    public class CustomRetryHandler implements HttpRequestRetryHandler {
        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount >= 3) {
                // 超过最大重试次数,放弃重试
                return false;
            }
            if (exception instanceof NoHttpResponseException) {
                // 对于没有响应的异常,进行重试
                return true;
            }
            // 其他异常根据需求决定是否重试
            return false;
        }
    }
    
  2. 将其配置到 HttpClient

        @Test
        public void testRetry(){
            HttpRequestRetryHandler retryHandler = new CustomRetryHandler();
    
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setRetryHandler(retryHandler)
                    .build();
        }
    

3.5 异步请求

Apache HttpClient 的异步请求是通过 HttpAsyncClient 实现的,允许你在发送请求后立即继续执行其他操作,而不是阻塞等待响应。这个特性对于需要高并发和低延迟的网络通信非常有用。

使用异步请求需要导入依赖:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
    <version>4.1.4</version> 
</dependency>

基本概念:

  • CloseableHttpAsyncClient:异步客户端,提供了异步发送请求和接收响应的能力。
  • HttpAsyncRequestProducer:异步请求生成器,创建要发送的请求。
  • HttpAsyncResponseConsumer:异步响应消费者,处理接收到的响应。
  • FutureCallback:处理异步操作结果的回调接口。
    @Test
    public void testAsync() throws IOException {
        CountDownLatch latch = new CountDownLatch(1); // 用于等待异步操作完成

        // 创建异步HttpClient
        try(CloseableHttpAsyncClient httpClient=HttpAsyncClients.createDefault()){
            // 启动HttpClient
            httpClient.start();
            // 创建异步请求
            HttpAsyncRequestProducer requestProducer = HttpAsyncMethods.createGet("http://www.example.com");
            HttpAsyncResponseConsumer<HttpResponse> responseConsumer = new BasicAsyncResponseConsumer();
            // 执行异步请求
            httpClient.execute(requestProducer, responseConsumer, new FutureCallback<HttpResponse>() {
                @Override
                public void completed(HttpResponse response) {
                    // 处理响应
                    System.out.println("Response received: " + response.getStatusLine());
                    latch.countDown();//通知主线程异步操作完成
                }

                @Override
                public void failed(Exception ex) {
                    // 处理请求失败
                    ex.printStackTrace();
                    latch.countDown();//通知主线程异步操作完成
                }

                @Override
                public void cancelled() {
                    // 处理请求取消
                    System.out.println("Request was cancelled.");
                    latch.countDown();//通知主线程异步操作完成
                }
            });
            latch.await();//等待异步操作完成
        }catch (Exception e){
            e.printStackTrace();
        }
    }

3.6 拦截器

Apache HttpClient 的 拦截器(Interceptors) 是一种强大的机制,允许你在 HTTP 请求/响应的处理链中插入自定义逻辑(例如日志记录、重试、认证等)。拦截器基于责任链模式,可以在请求发送前或响应返回后修改数据。

3.6.1 拦截器类型

HttpClient 提供了两种主要拦截器:

  • 请求拦截器:在请求发送到服务器前执行(如添加公共请求头)。
  • 响应拦截器:在收到服务器响应后执行(如日志记录或错误处理)。

3.6.2 快速入门

示例1:添加全局请求头

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;

public class HeaderInterceptorExample {
    public static void main(String[] args) throws Exception {
        
        // 1. 创建 HttpClient 并添加拦截器
        try (CloseableHttpClient httpClient = HttpClients.custom()
                .addRequestInterceptor((request, entity, context) -> {
                    // 为所有请求添加公共请求头
                    request.setHeader("User-Agent", "Apache HttpClient Demo");
                    request.setHeader("X-Custom-Header", "Hello");
                })
                .build()) {

            // 2. 发送请求
            HttpGet request = new HttpGet("https://httpbin.org/get");
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println(EntityUtils.toString(response.getEntity()));
            }
            
        }
    }
}

示例2:记录请求和响应日志

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;

public class LoggingInterceptorExample {
    public static void main(String[] args) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.custom()
                // 请求拦截器(记录请求信息)
                .addRequestInterceptor((request, entity, context) -> {
                    System.out.println(">>> Request: " + request.getMethod() + " " + request.getRequestUri());
                    System.out.println(">>> Headers: " + Arrays.toString(request.getHeaders()));
                })
                // 响应拦截器(记录响应信息)
                .addResponseInterceptor((response, entity, context) -> {
                    System.out.println("<<< Response: " + response.getCode());
                    System.out.println("<<< Headers: " + Arrays.toString(response.getHeaders()));
                })
                .build()) {

            HttpGet request = new HttpGet("https://httpbin.org/get");
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println(EntityUtils.toString(response.getEntity()));
            }
        }
    }
}

3.6.3 自定义拦截器

实现 HttpRequestInterceptor / HttpResponseInterceptor

import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.protocol.HttpContext;

public class CustomRequestInterceptor implements HttpRequestInterceptor {
    @Override
    public void process(HttpRequest request, HttpContext context) {
        request.setHeader("X-Auth-Token", "123456");
        System.out.println("Custom Interceptor: Added Auth Token");
    }
}

使用方式

CloseableHttpClient httpClient = HttpClients.custom()
        .addRequestInterceptor(new CustomRequestInterceptor())
        .build();

这个是请求拦截器,如果要实现响应拦截器只需要实现HttpResponseInterceptor即可