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

发布时间:2021-06-22 15:23:21 作者:chen
来源:亿速云 阅读:1209
# 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();

4. 协议不匹配

解决方案大全

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

// 创建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格式规范。内容从浅入深,既包含快速解决方案,也提供了根本性解决思路。

推荐阅读:
  1. Nginx出现双跨域问题解决
  2. cdh中hive创建表出现中文乱码问题

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

closeable httpclient

上一篇:php如何实现斐波那契数列

下一篇:python怎么判断两个ip是否属于同一个子网

相关阅读

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

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