CloseableHttpClient 类 的 public <T> T execute(final ClassicHttpRequest request, final HttpClientResponseHandler<? extends T> responseHandler ) 的get和post及记录日志
CloseableHttpClient
的 execute
方法通过指定的 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 对象,甚至是自定义的数据类型。
使用步骤
- 创建
ClassicHttpRequest
对象,例如HttpGet
、HttpPost
等请求类型。 - 创建一个实现
HttpClientResponseHandler
的实例,定义响应处理逻辑。 - 使用
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();
}
}
}
代码解读
-
创建请求对象:使用
HttpGet
创建了一个 GET 请求。 -
定义响应处理器:使用
HttpClientResponseHandler<String>
实现了一个处理器来处理响应。- 当响应状态码在 200 到 299 之间时,将响应体解析为字符串并返回。
- 否则,抛出异常表示处理失败。
-
执行请求:调用
execute(request, responseHandler)
发送请求。- 此方法会阻塞,直到收到响应并通过
responseHandler
进行处理。 - 返回结果为
String
,即处理器处理后的数据。
- 此方法会阻塞,直到收到响应并通过
-
输出结果:将处理后的响应输出到控制台。
当响应状态码在 200 到 299 之间时分别有如下一些情况:
在 HTTP 协议中,状态码 2xx
表示请求成功。每个具体的 2xx
状态码表示一种特定的成功情况:
常见的 2xx
状态码及其含义
-
200 OK
- 表示请求成功,并且服务器已成功处理并返回了请求的数据。
- 适用于几乎所有的成功请求场景,例如
GET
、POST
、PUT
、DELETE
请求的成功处理。
-
201 Created
- 表示服务器成功创建了资源,通常在
POST
请求中使用。 - 响应中可能包含指向新创建资源的 URI。
- 例如,向服务器发送一个创建新用户的
POST
请求,成功后返回201 Created
,并可能包含新用户的 URI。
- 表示服务器成功创建了资源,通常在
-
202 Accepted
- 请求已被服务器接受,但尚未被处理。
- 适用于异步处理请求的情况。请求可能在稍后才会被实际处理。
- 例如,大型数据处理或异步任务提交时,服务器可以先返回
202 Accepted
,然后在后台处理请求。
-
203 Non-Authoritative Information
- 表示请求成功,但返回的元数据(如响应头或部分响应数据)可能并非源自服务器的原始数据。
- 通常是在代理服务器或缓存服务器返回的响应,不是服务器的原始版本。
- 使用场景不常见。
-
204 No Content
- 请求成功,但没有返回任何内容。
- 适用于无需返回数据的操作,例如成功执行
DELETE
请求时返回204 No Content
。
-
205 Reset Content
- 请求成功,但客户端应重置显示,通常用于刷新表单或清除输入内容。
- 适用于需要客户端重置输入状态的场景,例如表单提交成功后,服务器可以返回
205
,提示客户端清除表单内容。
-
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
使用 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());
}
}
}
代码解析
-
创建
HttpPost
请求:使用HttpPost
设置请求的 URL 和参数。- 设置参数时,使用了
UrlEncodedFormEntity
,将参数转换为表单格式。
- 设置参数时,使用了
-
自定义
HttpClientResponseHandler
:实现了HttpClientResponseHandler
接口,定义了对响应的处理。statusCode
:获取并记录响应的状态码。responseBody
:通过EntityUtils.toString(entity)
获取并记录响应实体内容。response.getHeaders()
:遍历并记录每个响应头。
-
异常处理:捕获
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
方法可以捕获所有状态码,包括成功和失败的状态码。 - EntityUtils:
EntityUtils.toString(entity)
可以将HttpEntity
转为字符串,便于记录,但请注意它会将实体消耗掉,不能重复读取。
由于 String responseBody = httpClient.execute(postRequest, responseHandler); 可能会抛出 IOException, 有没有可能在请求成功的情况下抛出 IOException?
在调用 httpClient.execute(postRequest, responseHandler)
时,即使请求成功,也可能会在一些特定情况下抛出 IOException
。这些情况通常与网络连接问题、流处理、数据读取问题等有关,而不是响应的 HTTP 状态码。以下是一些可能在请求成功的情况下仍抛出 IOException
的情况:
可能抛出 IOException
的情况
-
连接中断或超时:即使服务器返回了响应,若在客户端读取响应数据时网络连接中断或发生超时,就会抛出
IOException
。这种情况较为常见,尤其是当客户端的网络状态不稳定或服务器响应较慢时。 -
响应体读取失败:在
responseHandler
处理响应时,通过EntityUtils.toString(entity)
读取HttpEntity
。如果在将流读取为字符串的过程中发生错误,例如流的格式无效,或者数据量过大导致内存不足,就会抛出IOException
。 -
响应头读取失败:在解析响应头或遍历响应头时,如果发生 IO 错误(例如流被意外关闭或数据不可访问),也会抛出
IOException
。 -
响应解压缩失败:如果服务器的响应使用了
gzip
等压缩方式,而客户端在解压缩时遇到数据损坏或格式错误,也会抛出IOException
。 -
连接池异常:
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 问题下都能准确记录响应的状态信息。