CloseableHttpClient 类 的 public <T> T execute(final ClassicHttpRequest request, final HttpClientResponseHandler<? extends T> responseHandler ) 的get和post及记录日志

CloseableHttpClientexecute 方法通过指定的 HttpClientResponseHandler 对响应进行处理。HttpClientResponseHandler 是一个回调接口,允许我们在响应返回时对其进行自定义处理。这个方法特别有用,因为可以在 execute 调用中直接处理响应,不需要将响应内容提取出来再进行额外的处理。

方法签名

public <T> T execute(final ClassicHttpRequest request, final HttpClientResponseHandler<? extends T> responseHandler) throws IOException
  • ClassicHttpRequest request:这是请求对象,包含了要发送的 HTTP 请求。
  • HttpClientResponseHandler<? extends T> responseHandler:这是对响应进行处理的回调接口,允许我们自定义响应处理逻辑并返回处理后的结果。
  • <T>:方法的返回值类型,由 HttpClientResponseHandler 决定。可以是字符串、JSON 对象,甚至是自定义的数据类型。

使用步骤

  1. 创建 ClassicHttpRequest 对象,例如 HttpGetHttpPost 等请求类型。
  2. 创建一个实现 HttpClientResponseHandler 的实例,定义响应处理逻辑。
  3. 使用 execute 方法发送请求,并获得响应处理结果。

示例代码

以下是一个完整的示例,其中 HttpClientResponseHandler 被用于处理 GET 请求的响应并返回响应内容作为字符串:

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.ClassicHttpRequest;
import org.apache.hc.client5.http.classic.CloseableHttpClient;
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.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import java.io.IOException;

public class HttpClientExample {
    public static void main(String[] args) {
        // 创建 HttpClient
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建请求对象
            ClassicHttpRequest request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");

            // 创建响应处理器
            HttpClientResponseHandler<String> responseHandler = response -> {
                int status = response.getCode();
                if (status >= 200 && status < 300) {
                    // 处理响应内容
                    return EntityUtils.toString(response.getEntity());
                } else {
                    throw new IOException("Unexpected response status: " + status);
                }
            };

            // 发送请求并获取响应
            String responseBody = httpClient.execute(request, responseHandler);
            System.out.println("Response: " + responseBody);

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

代码解读

  1. 创建请求对象:使用 HttpGet 创建了一个 GET 请求。

  2. 定义响应处理器:使用 HttpClientResponseHandler<String> 实现了一个处理器来处理响应。

    • 当响应状态码在 200 到 299 之间时,将响应体解析为字符串并返回。
    • 否则,抛出异常表示处理失败。
  3. 执行请求:调用 execute(request, responseHandler) 发送请求。

    • 此方法会阻塞,直到收到响应并通过 responseHandler 进行处理。
    • 返回结果为 String,即处理器处理后的数据。
  4. 输出结果:将处理后的响应输出到控制台。


当响应状态码在 200 到 299 之间时分别有如下一些情况:

在 HTTP 协议中,状态码 2xx 表示请求成功。每个具体的 2xx 状态码表示一种特定的成功情况:

常见的 2xx 状态码及其含义

  1. 200 OK

    • 表示请求成功,并且服务器已成功处理并返回了请求的数据。
    • 适用于几乎所有的成功请求场景,例如 GETPOSTPUTDELETE 请求的成功处理。
  2. 201 Created

    • 表示服务器成功创建了资源,通常在 POST 请求中使用。
    • 响应中可能包含指向新创建资源的 URI。
    • 例如,向服务器发送一个创建新用户的 POST 请求,成功后返回 201 Created,并可能包含新用户的 URI。
  3. 202 Accepted

    • 请求已被服务器接受,但尚未被处理。
    • 适用于异步处理请求的情况。请求可能在稍后才会被实际处理。
    • 例如,大型数据处理或异步任务提交时,服务器可以先返回 202 Accepted,然后在后台处理请求。
  4. 203 Non-Authoritative Information

    • 表示请求成功,但返回的元数据(如响应头或部分响应数据)可能并非源自服务器的原始数据。
    • 通常是在代理服务器或缓存服务器返回的响应,不是服务器的原始版本。
    • 使用场景不常见。
  5. 204 No Content

    • 请求成功,但没有返回任何内容。
    • 适用于无需返回数据的操作,例如成功执行 DELETE 请求时返回 204 No Content
  6. 205 Reset Content

    • 请求成功,但客户端应重置显示,通常用于刷新表单或清除输入内容。
    • 适用于需要客户端重置输入状态的场景,例如表单提交成功后,服务器可以返回 205,提示客户端清除表单内容。
  7. 206 Partial Content

    • 表示服务器成功处理了部分请求的数据。
    • 通常在实现 HTTP 分块下载时使用。客户端可以通过 Range 头请求部分资源,而服务器返回 206 Partial Content 及请求的部分内容。
    • 适用于大型文件的分段传输,支持暂停和恢复下载。

其他可能的 2xx 状态码

一些不太常见的 2xx 状态码可能出现在特定的协议或场景下,如 207 Multi-Status,通常在 WebDAV 扩展中使用,表示多个资源的状态,但在常规 HTTP 应用中很少使用。

总结

  • 200:请求成功并返回数据。
  • 201:资源创建成功(用于 POST 请求)。
  • 202:请求已接受,待处理。
  • 203:请求成功,返回的可能不是服务器的原始数据。
  • 204:请求成功,无返回数据。
  • 205:请求成功,要求客户端重置显示。
  • 206:请求成功,返回部分数据(用于分块下载)。

这些 2xx 状态码帮助客户端明确知道请求结果和服务器执行的具体操作。


CloseableHttpClient 类 的 public T execute(final ClassicHttpRequest request, final HttpClientResponseHandler<? extends T> responseHandler ) 如何发送 post 请求,并且要求无论什么情况下都要在日志中记录 status code,记录responseEntity, 记录 response headers

使用 CloseableHttpClient 类的 execute 方法发送 POST 请求并记录日志时,可以在自定义的 HttpClientResponseHandler 中实现该逻辑。HttpClientResponseHandler 会在请求成功和失败的情况下捕获响应状态码、响应实体以及响应头。

示例代码

下面的代码演示了如何实现这一要求。使用了 HttpPost 构建请求,通过自定义的 HttpClientResponseHandler 处理响应,将状态码、响应实体以及响应头写入日志:

import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.ClassicHttpRequest;
import org.apache.hc.client5.http.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.io.entity.UrlEncodedFormEntity;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

public class HttpClientPostExample {
    private static final Logger logger = Logger.getLogger(HttpClientPostExample.class.getName());

    public static void main(String[] args) {
        // 创建 HttpClient
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建 POST 请求
            HttpPost postRequest = new HttpPost("https://jsonplaceholder.typicode.com/posts");

            // 设置请求参数
            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair("title", "foo"));
            params.add(new BasicNameValuePair("body", "bar"));
            params.add(new BasicNameValuePair("userId", "1"));
            postRequest.setEntity(new UrlEncodedFormEntity(params));

            // 自定义响应处理器
            HttpClientResponseHandler<String> responseHandler = new HttpClientResponseHandler<String>() {
                @Override
                public String handleResponse(ClassicHttpResponse response) throws IOException {
                    int statusCode = response.getCode();
                    HttpEntity entity = response.getEntity();
                    String responseBody = entity != null ? EntityUtils.toString(entity) : null;

                    // 记录状态码
                    logger.info("Status Code: " + statusCode);

                    // 记录响应体
                    logger.info("Response Entity: " + responseBody);

                    // 记录响应头
                    response.getHeaders().forEach(header -> 
                        logger.info("Header: " + header.getName() + " = " + header.getValue())
                    );

                    // 返回响应体
                    return responseBody;
                }
            };

            // 执行请求
            String responseBody = httpClient.execute(postRequest, responseHandler);
            System.out.println("Response: " + responseBody);

        } catch (IOException e) {
            e.printStackTrace();
            logger.severe("Request failed: " + e.getMessage());
        }
    }
}

代码解析

  1. 创建 HttpPost 请求:使用 HttpPost 设置请求的 URL 和参数。

    • 设置参数时,使用了 UrlEncodedFormEntity,将参数转换为表单格式。
  2. 自定义 HttpClientResponseHandler:实现了 HttpClientResponseHandler 接口,定义了对响应的处理。

    • statusCode:获取并记录响应的状态码。
    • responseBody:通过 EntityUtils.toString(entity) 获取并记录响应实体内容。
    • response.getHeaders():遍历并记录每个响应头。
  3. 异常处理:捕获 IOException,在请求失败时记录日志。

日志输出示例

执行此代码时,日志会输出如下信息:

INFO: Status Code: 201
INFO: Response Entity: {"id":101,"title":"foo","body":"bar","userId":1}
INFO: Header: Content-Type = application/json; charset=utf-8
INFO: Header: Content-Length = 48
...

注意事项

  • 状态码范围handleResponse 方法可以捕获所有状态码,包括成功和失败的状态码。
  • EntityUtilsEntityUtils.toString(entity) 可以将 HttpEntity 转为字符串,便于记录,但请注意它会将实体消耗掉,不能重复读取。

由于 String responseBody = httpClient.execute(postRequest, responseHandler); 可能会抛出 IOException, 有没有可能在请求成功的情况下抛出 IOException?

在调用 httpClient.execute(postRequest, responseHandler) 时,即使请求成功,也可能会在一些特定情况下抛出 IOException。这些情况通常与网络连接问题、流处理、数据读取问题等有关,而不是响应的 HTTP 状态码。以下是一些可能在请求成功的情况下仍抛出 IOException 的情况:

可能抛出 IOException 的情况

  1. 连接中断或超时:即使服务器返回了响应,若在客户端读取响应数据时网络连接中断或发生超时,就会抛出 IOException。这种情况较为常见,尤其是当客户端的网络状态不稳定或服务器响应较慢时。

  2. 响应体读取失败:在 responseHandler 处理响应时,通过 EntityUtils.toString(entity) 读取 HttpEntity。如果在将流读取为字符串的过程中发生错误,例如流的格式无效,或者数据量过大导致内存不足,就会抛出 IOException

  3. 响应头读取失败:在解析响应头或遍历响应头时,如果发生 IO 错误(例如流被意外关闭或数据不可访问),也会抛出 IOException

  4. 响应解压缩失败:如果服务器的响应使用了 gzip 等压缩方式,而客户端在解压缩时遇到数据损坏或格式错误,也会抛出 IOException

  5. 连接池异常CloseableHttpClient 使用了连接池,如果连接池因配置或资源限制导致连接不可用,或连接池状态异常,则可能在请求成功但尚未读取数据时抛出 IOException

如何捕获和记录这些异常

为了确保在 IOException 出现时仍然能记录状态码、响应体、响应头等信息,可以在 responseHandler 中进一步处理这些异常。例如,通过 try-catch 包裹读取操作,在读取失败时捕获异常并记录信息。以下是改进后的代码示例:

HttpClientResponseHandler<String> responseHandler = new HttpClientResponseHandler<String>() {
    @Override
    public String handleResponse(ClassicHttpResponse response) throws IOException {
        int statusCode = response.getCode();
        String responseBody = null;

        try {
            // 获取并记录状态码
            logger.info("Status Code: " + statusCode);

            // 获取并记录响应体
            HttpEntity entity = response.getEntity();
            responseBody = entity != null ? EntityUtils.toString(entity) : null;
            logger.info("Response Entity: " + responseBody);

            // 记录响应头
            response.getHeaders().forEach(header ->
                logger.info("Header: " + header.getName() + " = " + header.getValue())
            );

        } catch (IOException e) {
            logger.severe("Error while reading response: " + e.getMessage());
            throw e; // 重新抛出以便捕获此异常
        }
        return responseBody;
    }
};

其他处理建议

在调用 httpClient.execute 时还可以使用外围 try-catch,确保无论 IOException 出现在何处,都可以捕获并记录异常:

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
    String responseBody = httpClient.execute(postRequest, responseHandler);
    System.out.println("Response: " + responseBody);
} catch (IOException e) {
    logger.severe("Request failed with IOException: " + e.getMessage());
}

总结

在请求成功但读取数据或处理响应过程中仍可能抛出 IOException,而不是由响应状态码本身引发。通过在 responseHandler 内部及外部分别进行 try-catch 捕获,可以确保在各类 IO 问题下都能准确记录响应的状态信息。

posted @ 2024-10-28 00:32  gongchengship  阅读(52)  评论(0编辑  收藏  举报