温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

CloseableHttpClient出现Premature end of Content-Length delimited message body怎么解决

发布时间:2021-06-22 15:23:21 来源:亿速云 阅读:1240 作者:chen 栏目:编程语言
# CloseableHttpClient出现Premature end of Content-Length delimited message body怎么解决 ## 问题现象描述 在使用Apache HttpClient的`CloseableHttpClient`进行HTTP请求时,开发者可能会遇到如下异常: 

org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: X; received: Y)

 这个错误表明: 1. 服务器在响应头中声明了`Content-Length: X`(具体字节数) 2. 但实际传输过程中,响应体在接收到Y字节后连接就被意外关闭 3. 客户端期望接收X字节,但只收到Y字节(Y < X) ## 错误原因深度分析 ### 1. 网络层问题 - **不稳定的网络连接**:请求过程中网络中断 - **代理服务器干扰**:中间代理服务器修改了响应内容但未正确更新Content-Length - **防火墙拦截**:企业防火墙可能截断了大数据量响应 ### 2. 服务端问题 - **服务器实现缺陷**: - 计算Content-Length错误 - 未正确处理大文件分块传输 - 未实现完整的HTTP协议规范 - **应用层异常**: - 服务端处理请求时抛出未捕获异常 - 线程被意外终止 - 服务端资源耗尽(内存、连接数等) ### 3. HttpClient配置问题 - **连接超时设置不当**: ```java RequestConfig config = RequestConfig.custom() .setConnectTimeout(5000) // 连接建立超时 .setSocketTimeout(30000) // 数据传输超时 .build(); 
  • 未启用重试机制
     HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(3, true); 

4. 协议不匹配

  • 服务器实际使用分块传输编码(chunked encoding)但错误设置了Content-Length
  • HTTP/1.1与HTTP/2协议处理差异

解决方案大全

方案一:配置连接超时和重试机制

// 创建HttpClient时加入重试和超时配置 CloseableHttpClient httpClient = HttpClients.custom() .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) .setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(5000) .setSocketTimeout(30000) .build()) .build(); 

参数说明: - 重试次数建议3次 - socketTimeout应根据响应数据量调整(大文件需要更长时间)

方案二:禁用严格的内容长度验证

CloseableHttpClient httpClient = HttpClients.custom() .disableContentCompression() .setContentCompressionStrategy(ContentCompressionStrategy.NO_COMPRESSION) .build(); 

或者使用更激进的策略:

HttpClientBuilder builder = HttpClients.custom(); builder.disableContentCompression(); builder.setContentCompressionStrategy(ContentCompressionStrategy.NO_COMPRESSION); builder.setDefaultRequestConfig(RequestConfig.custom() .setStaleConnectionCheckEnabled(true) .build()); 

方案三:使用响应拦截器处理异常

public class MyResponseInterceptor implements HttpResponseInterceptor { @Override public void process(HttpResponse response, HttpContext context) { // 检查Content-Length与实际接收数据是否匹配 Header contentLength = response.getFirstHeader("Content-Length"); if (contentLength != null) { try { int declaredLength = Integer.parseInt(contentLength.getValue()); // 可以在此处添加自定义校验逻辑 } catch (NumberFormatException e) { response.removeHeaders("Content-Length"); } } } } // 使用拦截器 CloseableHttpClient httpClient = HttpClients.custom() .addInterceptorLast(new MyResponseInterceptor()) .build(); 

方案四:改用分块传输模式

如果服务端支持,可以强制使用分块传输:

HttpPost httpPost = new HttpPost("http://example.com/api"); httpPost.setHeader("Transfer-Encoding", "chunked"); 

方案五:自定义EntityUtils

替换默认的响应体解析方法:

public static String toString(HttpEntity entity, Charset defaultCharset) throws IOException { try (InputStream instream = entity.getContent()) { if (instream == null) { return null; } // 不依赖entity.getContentLength() ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = instream.read(buffer)) != -1) { baos.write(buffer, 0, len); } return baos.toString(defaultCharset.name()); } } 

最佳实践建议

1. 客户端配置模板

public CloseableHttpClient createResilientHttpClient() { return HttpClients.custom() .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) .setDefaultRequestConfig(RequestConfig.custom() .setConnectTimeout(10000) .setSocketTimeout(60000) .setConnectionRequestTimeout(5000) .build()) .setConnectionManager(new PoolingHttpClientConnectionManager()) .addInterceptorLast(new ContentLengthFixInterceptor()) .build(); } 

2. 服务端协作方案

建议服务端: 1. 对于动态内容,优先使用Transfer-Encoding: chunked 2. 确保正确计算静态资源的Content-Length 3. 实现HTTP/1.1的100 Continue机制

3. 监控与日志

添加详细日志记录:

HttpClientBuilder builder = HttpClients.custom(); builder.setConnectionManager(new PoolingHttpClientConnectionManager() { @Override public void closeExpiredConnections() { logger.debug("Closing expired connections"); super.closeExpiredConnections(); } }); builder.addInterceptorFirst(new HttpRequestInterceptor() { @Override public void process(HttpRequest request, HttpContext context) { logger.debug("Request headers: {}", Arrays.toString(request.getAllHeaders())); } }); 

高级调试技巧

1. 使用Wireshark抓包分析

关键过滤条件:

http && (tcp.port == 80 || tcp.port == 443) 

检查: 1. 服务端发送的Content-Length值 2. 实际TCP流结束位置 3. 是否有RST包异常终止连接

2. 启用HttpClient调试日志

在log4j.properties中添加:

log4j.logger.org.apache.http=DEBUG log4j.logger.org.apache.http.wire=ERROR 

3. 模拟测试用例

@Test(expected = HttpClientError.class) public void testIncompleteResponse() throws Exception { // 启动一个故意发送不完整响应的测试服务器 MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse() .setBody("incomplete data") .setHeader("Content-Length", "100")); server.start(); // 使用HttpClient请求测试服务器 CloseableHttpClient client = createHttpClient(); HttpGet get = new HttpGet(server.url("/").toString()); HttpResponse response = client.execute(get); // 应该抛出异常 EntityUtils.toString(response.getEntity()); } 

相关源码分析

关键类分析: 1. ContentLengthInputStream (org.apache.http.entity) - 负责验证实际读取字节数与Content-Length是否匹配 - 抛出异常的代码位置:

 if (this.contentLength >= 0 && pos >= this.contentLength) { throw new ConnectionClosedException( "Premature end of Content-Length delimited message body"); } 

  1. HttpClientConnection的实现
    • 管理底层Socket连接
    • 处理连接超时和重置

替代方案比较

方案 优点 缺点 适用场景
增加超时时间 简单直接 不能解决服务端问题 网络不稳定的移动环境
禁用长度验证 避免异常 可能接收不完整数据 非关键性数据获取
使用分块传输 符合HTTP/1.1规范 需要服务端支持 大文件下载
自定义解析 完全控制流程 开发成本高 特殊业务需求

总结

解决Premature end of Content-Length错误的根本思路是: 1. 客户端增强健壮性:合理配置超时、重试机制 2. 服务端确保规范:正确实现HTTP协议 3. 网络环境优化:确保连接稳定性

对于关键业务系统,建议同时采用客户端防御性编程和服务端完善相结合的方式,才能从根本上解决这类问题。 “`

这篇文章共计约2900字,包含了: 1. 问题现象描述 2. 深度原因分析(分4大类) 3. 5种具体解决方案(含代码示例) 4. 最佳实践建议 5. 高级调试技巧 6. 源码分析 7. 方案比较表格 8. 总结建议

所有代码示例都使用Java语言展示,并保持markdown格式规范。内容从浅入深,既包含快速解决方案,也提供了根本性解决思路。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI