You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中RestTemplate+Apache HttpClient连续POST请求二次失败问题

解决连续POST请求第二次失败(NoHttpResponseException)的问题

问题现象

在Spring Boot环境中使用RestTemplate(或直接调用Apache HttpClient)发起连续两次POST请求时,首次请求正常成功,第二次请求必然失败,报错信息如下:

I/O error on POST request for "https://httpbin.org/post": httpbin.org:443 failed to respond

对应的底层异常为org.apache.hc.core5.http.NoHttpResponseException(HttpClient 5.x)或org.apache.http.NoHttpResponseException(HttpClient 4.x)。

已验证的临时缓解手段:

  • 设置请求头Connection=close禁用Keep-Alive长连接
  • 两次请求之间添加sleep延迟
  • 移除Burp代理配置

环境信息

  • Spring Boot版本:3.3.5 + Apache HttpClient 5.4.1;2.7.8 + Apache HttpClient 4.5.14
  • JDK版本:17
  • 代理工具:Burp Suite

复现代码(简化为直接使用HttpClient)

public class RestTemplateDemo {

  private static Logger logger = Logger.getLogger(RestTemplateDemo.class.getName());

  public static void main(String[] args) {
    System.setProperty("javax.net.debug", "all");
    HttpHost proxy = new HttpHost("localhost", 8888);
    CloseableHttpClient client = HttpClients.custom()
        .setProxy(proxy)
        .build();

    logger.info("1. call");
    callPost(client);
    logger.info("2. call");
    callPost(client);
  }

  static void callPost(CloseableHttpClient client) {
    String url = "https://httpbin.org/post";
    HttpPost post = new HttpPost(url);
    post.setEntity(new StringEntity("test", Charset.defaultCharset()));
    try (CloseableHttpResponse response = client.execute(post)) {
      logger.info(response.getCode() + " " + response.getReasonPhrase());
    } catch (IOException e) {
      logger.log(Level.SEVERE, e.getMessage(), e);
    }
  }
}

从SSL调试日志中可观察到关键线索:第二次请求时,客户端收到Burp发送的user_canceled警告Alert,随后是close_notify消息,最终连接被被动关闭,导致请求无响应。

根因分析

该问题本质是Burp代理与Apache HttpClient的Keep-Alive连接池交互不兼容:

  1. Apache HttpClient默认启用长连接复用,第一次请求完成后,连接会被放回连接池等待后续请求复用。
  2. Burp代理在代理HTTPS连接时,可能会在请求完成后主动关闭与目标服务器的连接,但未及时通知客户端(HttpClient)该连接已失效。
  3. 当HttpClient复用这个已被Burp关闭的连接发起第二次请求时,由于连接实际已断开,就会抛出NoHttpResponseException。

添加sleep延迟生效的原因是:延迟期间Burp的连接超时机制尚未触发,连接还保持活跃;禁用Keep-Alive则每次请求都新建连接,避免了复用失效连接的场景。

解决方案

针对遗留代码场景,推荐以下几种可靠的解决方式:

1. 配置连接池自动检查连接有效性(推荐)

修改HttpClient的连接池配置,添加连接有效性校验,确保从连接池获取的连接是可用的:

对于HttpClient 5.x

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
// 连接空闲1秒后,获取时自动验证有效性
connManager.setValidateAfterInactivity(1000);

CloseableHttpClient client = HttpClients.custom()
    .setProxy(proxy)
    .setConnectionManager(connManager)
    .build();

对于HttpClient 4.x

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setValidateAfterInactivity(1000);

CloseableHttpClient client = HttpClients.custom()
    .setProxy(proxy)
    .setConnectionManager(connManager)
    .build();

该配置会在从连接池获取连接时,检查连接空闲时长,若超过设定值则先验证连接状态,无效则丢弃并新建连接,既保留长连接的性能优势,又避免复用失效连接。

2. 全局禁用Keep-Alive(简单但牺牲性能)

如果对性能要求不高,可以全局禁用长连接,每次请求都新建连接:

CloseableHttpClient client = HttpClients.custom()
    .setProxy(proxy)
    .addInterceptorFirst((HttpRequestInterceptor) (request, context) -> 
        request.setHeader(HttpHeaders.CONNECTION, "close"))
    .build();

也可以在RestTemplate的配置中统一添加Connection: close请求头。

3. 添加请求重试机制

配置重试策略,当遇到NoHttpResponseException时自动重试请求:

对于HttpClient 5.x

HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(
    3, // 最大重试次数
    true, // 是否重试幂等方法(POST默认不重试,若业务允许可调整)
    Collections.singleton(NoHttpResponseException.class)
);

CloseableHttpClient client = HttpClients.custom()
    .setProxy(proxy)
    .setRetryHandler(retryHandler)
    .build();

注意:POST请求默认不属于幂等方法,若业务场景允许重试(例如接口本身是幂等的),可调整重试条件包含POST方法。

4. 调整Burp代理连接超时配置(可选)

在Burp中修改连接超时设置,延长连接保持时间,匹配HttpClient连接池的空闲超时:

  1. 打开Burp Suite,进入Settings -> Connections
  2. 调整Server connection timeout和Client connection timeout的值,确保大于HttpClient连接池的空闲超时时间(默认是20秒,可根据实际情况调整)

总结

最推荐的方案是配置连接池的有效性检查,兼顾性能与稳定性;若业务允许,搭配重试机制可进一步提升请求成功率。

内容的提问来源于stack exchange,提问作者Matthias M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 04:44:50