Apache HttpClient临时错误:NoHttpResponseException


76

我有一个Web服务正在接受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);但没有用。有人可以建议我现在可以做什么吗?

重要事项 除了弄清楚为什么我会得到例外之外,我还有一个重要的担忧是为什么重试处理程序不在这里工作?


我认为这与客户端代码无关。可能是目标服务器在处理响应时太忙了吗?
科萨2012年

1
我试着提琴手轰炸目标服务器,但是效果很好。我什至尝试使用提琴手执行相同的步骤来重现该错误,但不走运!
Em Ae

哪种Web服务器正在运行该服务,并且在10-15分钟的等待时间内,该服务是否正在接收其他请求,或者该服务是否处于空闲状态?
肖恩2012年

雄猫 它不是模拟服务atm,除了我的电话以外,没有收到其他任何东西。
Em Ae 2012年

我也收到此错误,但在ResourceAccessException的包装中。
萨加尔·哈拉布

Answers:


115

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

解决此问题的最简单方法是将过期的连接和闲置了一段时间(例如,在池中闲置一分钟之后)后空闲的连接从池中驱逐更长的时间。有关详细信息,请参见HttpClient教程的本节


我正在使用HTTTPClient 4.5.1,这里两个连续的重试服务器响应失败,但是第三次​​成功,所以为什么即使我将时间保持为1分钟也无法进行第一次尝试连接。
昆丹·阿特

@oleg,空闲连接和过期连接有什么区别?
Ales

1
某些连接可能具有到期时间,超过该时间,应将其视为不再有效/可重复使用。空闲连接完全有效且可重复使用,但暂时不使用。
ok2c

2
这取决于许多因素。它应该是安全的和幂方法,只要服务器符合HTTP规范与关于方法的安全性和幂等的安全。
ok2c

1
在这里建议使用一分钟超时正是我的问题所在:Apache客户端与本地码头服务器通信。默认情况下为AFAIK,在30秒钟不活动后,码头会关闭连接。设置connectionManager.setValidateAfterInactivity(500),即半秒钟解决了我的问题。我想,几秒钟也可以,但是我不在乎。
maaartinus

20

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


3
鉴于最初的问题指定了POST,重试处理程序是否是此处的正确方法?
Brian Agnew

2
您是在问POST是否可以重试吗?根据设计原则,PUT是幂等的。重试POST可能会导致意想不到的后果。再一次,并非所有的PUT都是幂等的。
activedecay

用我提供的方法重试POST请求几乎是完全安全的-因为它仅处理和重试NoHttpResponseException,这比客户端而不是服务器端更容易实现。
Jehy,

9
@Jehy根本不是绝对安全... NoHttpResponseException意味着客户端没有得到响应,不是服务器没有得到和/或处理请求
Lyrkan

如果在密钥/身份创建期间重新使用内容部分,则服务器可以吐出400并确认该条目已创建,因此可以通过实施确保安全性。
茄子

7

HttpClient 4.4在此区域中存在一个错误,该错误与在返回给请求者之前验证可能过时的连接有关。它没有验证连接是否陈旧,然后导致立即中断NoHttpResponseException

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


14
我正在使用4.5.3版本,但仍然会收到NoHttpResponseException。就我而言,我在每个第4个或第5个连续请求中都收到此Exception,而不是在第一个请求中。知道我的情况可能是什么原因吗?
Sahil Chhabra

1
@SahilChhabra在我这边也有同样的情况。您能找到解决问题的任何方法吗?
Manish Bansal,

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();
}

就我而言,我在每个第4个或第5个连续请求中都收到此Exception,而不是在第一个请求中。知道我的情况可能是什么原因吗?
Sahil Chhabra

+1好的详细答案,但是...恕我直言,这是针对Apache HttpClient错误行为的一种解决方法。正确的解决方案是切换到能够从旧连接(例如OkHttp)正常恢复的库。
G. Demecki

3

尽管公认的答案是正确的,但是恕我直言只是一种解决方法。

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

由于Apache HttpClient中的这种错误行为多年来没有得到修复,因此我绝对希望切换到可以轻松地从过时的连接问题中恢复的库,例如OkHttp。

为什么?

  1. OkHttp默认情况下会缓冲http连接。
  2. 它可以从http连接陈旧并且由于不等幂(例如POST)而无法重试请求的情况下正常恢复。我不能说Apache HttpClient(提到NoHttpResponseException)。
  3. 支持早期草稿和Beta版的HTTP / 2.0。

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


3

解决方案:将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());

2

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


您的评论对我有很大帮助。我遇到了同样的问题,但是使用Spring Cloud Zuul 1.3.2 RELEASE,并且在高负载下,我的解决方案是复制粘贴org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter并删除disableContentCompression调用,我没有更改依赖项版本,因为jar相当混乱
Artem Ptushkin

1

我在Apache HTTP Client 4.5.5上添加默认标头的相同问题

连接方式:关闭

解决问题


5
:facepalm:真是个坏主意。出于某些目的引入了HTTP世界中的持久连接。仅因为某些客户端库无法正确使用它们而放弃它们是一个巨大的错误。
G. Demecki

-2

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

步骤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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.