Java Springboot发送Http请求返回二进制数据及原理分析
Java Springboot发送Http请求返回二进制数据及原理分析
1、依赖
org.apache.httpcomponents
httpclient
2、工具类
/**
* @description:发送HTTP POST请求,参数为JSON格式,并返回二进制数据。
* @author: lizhiwei
* @date: 2023/12/15 15:59
* @param url 请求的URL地址。
* @param jsonParams 请求的参数,以JSON字符串形式提供。
* @param headParams 请求的头部参数,以键值对形式提供。
* @return: byte[] 服务器响应的二进制数据。
**/
public static byte[] doPostJsonForBytes(String url, String jsonParams, Map headParams) {
// 创建HTTP客户端实例
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
try {
// 创建HTTP POST请求对象
HttpPost httpPost = new HttpPost(url);
// 配置请求和传输超时时间
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build();
httpPost.setConfig(requestConfig);
// 设置请求体为JSON字符串
httpPost.setEntity(new StringEntity(jsonParams, ContentType.APPLICATION_JSON));
// 如果存在头部参数,则添加到请求头中
if (headParams != null) {
for (Map.Entry entry : headParams.entrySet()) {
httpPost.addHeader(entry.getKey(), entry.getValue());
}
}
// 执行请求并获取响应
response = httpClient.execute(httpPost);
// 检查响应状态码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "HTTP请求失败,状态码:" + statusCode);
}
// 获取响应二进制数据
HttpEntity entity = response.getEntity();
// String responseBody = EntityUtils.toString(entity);
// log.error("看看正确的错误的,返回的有什么区别?"+responseBody);
// 将响应实体转换为字节数组
byte[] responseBytes = EntityUtils.toByteArray(entity);
return responseBytes;
} catch (Exception e) {
log.error("执行HTTP POST请求异常:", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "执行HTTP POST请求异常:" + e.getMessage());
} finally {
// 关闭资源
HttpClientUtils.closeQuietly(response);
HttpClientUtils.closeQuietly(httpClient);
}
}
3、原理分析
3.1基础流程
- HTTP 协议简介: HTTP(超文本传输协议)是一个用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是一个基于请求-响应模式的无状态协议,通常运行在TCP/IP协议之上。
- 客户端和服务器模型: 在HTTP通信中,通常有一个客户端(例如一个Web浏览器或一个HTTP客户端库,如Apache HttpClient)和一个服务器(托管Web资源的服务器)。客户端发起请求,服务器响应请求。
- 域名解析: 当客户端要发送HTTP请求时,首先需要解析服务器的域名(如果提供的是域名而非IP地址)。这涉及到DNS(域名系统)查询,将域名转换为IP地址。
- 建立TCP连接: HTTP通常运行在TCP协议之上。在发送HTTP请求之前,客户端会与服务器建立一个TCP连接。这个过程通常包括三次握手,确保双方都准备好进行数据传输。
- 发送HTTP请求: 一旦TCP连接建立,客户端就会通过这个连接发送一个HTTP请求。这个请求包含:
- 请求行:包含方法(GET、POST等)、请求的资源的URI和HTTP版本。
- 请求头:包含关于客户端环境和请求体的元数据,例如User-Agent、Accept、Content-Type等。
- 请求体:对于某些类型的请求(如POST),这部分包含要发送给服务器的数据。
- 服务器处理请求: 服务器接收到HTTP请求后,会根据请求的资源、方法和其他参数处理请求。这可能涉及到访问数据库、执行服务器端程序等操作。
- 发送响应: 处理请求后,服务器会向客户端发送一个HTTP响应。这个响应包含:
- 状态行:包含HTTP状态码和状态消息,表示请求成功、失败或其他状态。
- 响应头:包含关于服务器、响应体和资源的信息,如Content-Type、Content-Length等。
- 响应体:包含请求的资源或其他数据。
- 关闭TCP连接: 数据传输完成后,TCP连接可以被关闭,或者根据HTTP头部中的Connection字段保持开放以供未来的请求复用。
- 无状态性和连接管理: HTTP是一个无状态协议,意味着每个请求都是独立的,服务器不保存两个请求之间的状态。但在现代HTTP应用中,通常使用cookies、会话等机制来维护状态。同时,持久连接、管道化等技术被用于优化连接管理和网络资源的使用。
3.2代码解析
-
创建HTTP客户端实例:
CloseableHttpClient httpClient = HttpClients.createDefault();
这行代码使用Apache HttpClient库创建了一个CloseableHttpClient对象。这个客户端用于执行HTTP请求。createDefault()方法会返回一个默认配置的CloseableHttpClient实例。
-
创建HTTP POST请求对象:
HttpPost httpPost = new HttpPost(url);
通过HttpPost类创建一个POST请求对象。这个对象包含了要发送到服务器的请求信息,其中url是请求的目标地址。
-
配置请求和传输超时时间:
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build(); httpPost.setConfig(requestConfig);
使用RequestConfig设置请求的超时配置。setSocketTimeout设置从服务器读取数据的超时时间,setConnectTimeout设置连接服务器的超时时间。这里两者都设置为100秒。然后将这个配置应用到httpPost对象上。
-
设置请求体为JSON字符串:
httpPost.setEntity(new StringEntity(jsonParams, ContentType.APPLICATION_JSON));
这行代码设置了HTTP POST请求的请求体。jsonParams是传入的JSON格式参数,它被转换成StringEntity,并设置其内容类型为ContentType.APPLICATION_JSON,表示发送的是JSON格式的数据。
-
添加头部参数:
if (headParams != null) { for (Map.Entry entry : headParams.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } }如果headParams(请求头参数)不为空,则遍历这个Map,并将每个键值对添加到HTTP请求的头部。
-
执行请求并获取响应:
response = httpClient.execute(httpPost);
使用httpClient执行httpPost请求,并接收响应。响应被保存在CloseableHttpResponse对象response中。
-
检查响应状态码:
int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "HTTP请求失败,状态码:" + statusCode); }从响应中获取HTTP状态码,如果状态码不是200(HttpStatus.SC_OK),则抛出业务异常,表示HTTP请求失败。
-
获取响应二进制数据:
HttpEntity entity = response.getEntity(); byte[] responseBytes = EntityUtils.toByteArray(entity); //这里补充一下,正常我们返回的是字符串,使用 //result = EntityUtils.toString(entity, "UTF-8");即可,我这个请求是有需要返回二进制数据,所以采用这样的方法,你可以根据需要自行改写获取响应体的格式并返回。
从响应中提取HttpEntity,然后使用EntityUtils.toByteArray(entity)将其转换为字节数组。这些字节是服务器返回的原始二进制数据。
-
异常处理:
catch (Exception e) { log.error("执行HTTP POST请求异常:", e); throw new BusinessException(ErrorCode.SYSTEM_ERROR, "执行HTTP POST请求异常:" + e.getMessage()); }如果在执行过程中出现任何异常,将捕获这些异常,记录错误日志,并抛出业务异常。
-
关闭资源:
finally { HttpClientUtils.closeQuietly(response); HttpClientUtils.closeQuietly(httpClient); }在finally块中,无论请求成功还是异常,都会关闭响应和客户端实例以释放资源。
这段代码的执行流程涵盖了创建客户端、配置请求、发送请求、处理响应和异常,是一个完整的用于发送HTTP POST请求并接收二进制响应的实现。
注意:
开发的过程中踩了一个坑,不能多次转换数据,转换一下,流已经关闭了,不能又一次读取数据
java.io.IOException: Attempted read from closed stream.
出现这个原因通常是因为尝试从已经关闭的流中读取数据。在你的代码中,这个问题很可能是由于以下原因造成的:
重复读取响应体: 在你的代码中,你首先使用 EntityUtils.toString(entity) 读取了响应体作为字符串,然后又尝试通过 EntityUtils.toByteArray(entity) 从相同的 HttpEntity 实例中读取数据。HttpEntity 只能被读取一次,读取后流就会关闭。因此,第二次尝试读取时会抛出 Attempted read from closed stream 异常。
解决方案
要解决这个问题,你可以只读取一次响应体。如果你需要同时获取响应体的字符串表示和字节数组表示,可以先读取为字节数组,然后将字节数组转换为字符串,而不是直接从HttpEntity中读取两次。
本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://www.net2asp.com/cb516fa7c3.html
