91

我有一个接受 XML POST 方法的网络服务。它工作正常,然后在某个随机场合,它无法与服务器通信,并使用 message 抛出 IOException The target server failed to respond。随后的调用工作正常。

它主要发生在我拨打一些电话然后让我的应用程序空闲 10-15 分钟时。之后我进行的第一次调用会返回此错误。

我尝试了几件事......

我设置了重试处理程序,如

HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            public boolean retryRequest(IOException e, int retryCount, HttpContext httpCtx) {
                if (retryCount >= 3){
                    Logger.warn(CALLER, "Maximum tries reached, exception would be thrown to outer block");
                    return false;
                }
                if (e instanceof org.apache.http.NoHttpResponseException){
                    Logger.warn(CALLER, "No response from server on "+retryCount+" call");
                    return true;
                }
                return false;
            }
        };

        httpPost.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);

但是这个重试从来没有被调用过。(是的,我正在使用正确的 instanceof 子句)。在调试此类时永远不会被调用。

我什至尝试设置HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);但没有用。有人可以建议我现在能做什么吗?

重要 除了弄清楚为什么会出现异常之外,我最关心的一个重要问题是为什么重试处理程序不在这里工作?

4

10 回答 10

133

连接管理器保持活动的最有可能的持久连接变得陈旧。也就是说,目标服务器在连接处于空闲状态时,在其端关闭连接而 HttpClient 无法对该事件做出反应,从而使连接处于半关闭状态或“陈旧”状态。通常这不是问题。HttpClient 使用多种技术在从池中租用时验证连接的有效性。即使禁用过时连接检查并且使用过时连接来传输请求消息,请求执行通常在写入操作中失败并出现 SocketException 并自动重试。然而,在某些情况下,写操作可以毫无例外地终止,随后的读操作返回 -1(流结束)。

补救这种情况的最简单方法是在一段时间不活动后从池中逐出过期连接和空闲时间超过 1 分钟的连接。有关详细信息,请参阅HttpClient 教程的这一部分

于 2012-05-15T12:34:35.973 回答
22

接受的答案是正确的,但缺乏解决方案。为避免此错误,您可以在此答案中为您的 HTTP 客户端添加 setHttpRequestRetryHandler(或 apache 组件 4.4 的 setRetryHandler) 。

于 2016-06-07T12:55:30.587 回答
7

HttpClient 4.4 在这个领域遇到了一个错误,该错误与在返回请求者之前验证可能陈旧的连接有关。它没有验证连接是否过时,然后立即生成NoHttpResponseException.

此问题已在 HttpClient 4.4.1 中解决。请参阅此 JIRA发行说明

于 2017-12-12T09:48:17.977 回答
5

虽然接受的答案是正确的,但恕我直言只是一种解决方法。

需要明确的是:持久连接可能会变得陈旧,这是一种完全正常的情况。但不幸的是,当 HTTP 客户端库无法正确处理它时,它是非常糟糕的。

由于 Apache HttpClient 中的这种错误行为多年未得到修复,我肯定更愿意切换到可以轻松从陈旧连接问题中恢复的库,例如 OkHttp。

为什么?

  1. OkHttp 默认池化 http 连接。
  2. 当 http 连接过时并且由于不是幂等(例如 POST)而无法重试请求时,它可以优雅地恢复。我不能说 Apache HttpClient(提到NoHttpResponseException)。
  3. 支持早期草稿和 beta 版本的 HTTP/2.0。

当我切换到 OkHttp 时,我的问题NoHttpResponseException永远消失了。

于 2019-06-03T10:05:04.643 回答
5

解决方案:将 ReuseStrategy 更改为 never

由于这个问题非常复杂,并且有很多不同的因素可能会失败,我很高兴在另一篇文章中找到了这个解决方案:如何解决 org.apache.http.NoHttpResponseException

永远不要重用连接:在 org.apache.http.impl.client.AbstractHttpClient 中配置:

httpClient.setReuseStrategy(new NoConnectionReuseStrategy());

可以在 org.apache.http.impl.client.HttpClientBuilder 构建器上配置相同的内容:

builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
于 2019-11-07T09:11:55.287 回答
4

如今,除非另有声明,否则大多数 HTTP 连接都被认为是持久的。但是,为了节省服务器资源,连接很少会永远保持打开状态,许多服务器的默认连接超时相当短,例如 Apache httpd 2.2 及更高版本为 5 秒。

org.apache.http.NoHttpResponseException错误很可能来自服务器关闭的一个持久连接。

可以设置在 Apache Http 客户端池中保持未使用连接打开的最长时间,以毫秒为单位。

使用 Spring Boot,实现此目的的一种方法:

public class RestTemplateCustomizers {
    static public class MaxConnectionTimeCustomizer implements RestTemplateCustomizer {

        @Override
        public void customize(RestTemplate restTemplate) {
            HttpClient httpClient = HttpClientBuilder
                .create()
                .setConnectionTimeToLive(1000, TimeUnit.MILLISECONDS)
                .build();

            restTemplate.setRequestFactory(
                new HttpComponentsClientHttpRequestFactory(httpClient));
        }
    }
}

// In your service that uses a RestTemplate
public MyRestService(RestTemplateBuilder builder ) {
    restTemplate = builder
         .customizers(new RestTemplateCustomizers.MaxConnectionTimeCustomizer())
         .build();
}
于 2017-12-16T16:48:43.263 回答
2

如果disableContentCompression()在分配给您的 HttpClient 的池管理器上设置,并且目标服务器正在尝试使用 gzip 压缩,则可能会发生这种情况。

于 2019-04-12T19:24:09.550 回答
1

在 apache http 客户端 4.5.5 上添加默认标头对我来说同样的问题

连接:关闭

解决问题

于 2018-04-13T16:07:37.213 回答
0

使用PoolingHttpClientConnectionManager代替BasicHttpClientConnectionManager

BasicHttpClientConnectionManager将努力为具有相同路由的后续请求重用连接。但是,它将关闭现有连接并为给定路由重新打开它。

于 2022-02-16T08:48:10.217 回答
-2

我遇到了同样的问题,我通过添加“连接:关闭”作为扩展来解决,

步骤 1:创建一个新类ConnectionCloseExtension

import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.HttpHeaders;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;

public class ConnectionCloseExtension extends ResponseTransformer {
  @Override
  public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
    return Response.Builder
        .like(response)
        .headers(HttpHeaders.copyOf(response.getHeaders())
            .plus(new HttpHeader("Connection", "Close")))
        .build();
  }

  @Override
  public String getName() {
    return "ConnectionCloseExtension";
  }
}

第2步:在wireMockServer中设置扩展类,如下所示,

final WireMockServer wireMockServer = new WireMockServer(options()
                .extensions(ConnectionCloseExtension.class)
                .port(httpPort));
于 2019-10-01T10:33:54.030 回答