【问题标题】:Apache HttpClient Interim Error: NoHttpResponseExceptionApache HttpClient 临时错误:NoHttpResponseException
【发布时间】:2012-05-20 11:44:02
【问题描述】:

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

这主要发生在我拨打一些电话然后让我的应用程序空闲 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);,但没有用。有人可以建议我现在能做什么吗?

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

【问题讨论】:

  • 我认为这与客户端代码无关。可能是目标服务器忙于处理响应?
  • 我尝试 fiddler 轰炸目标服务器,但效果很好。我什至尝试使用 fiddler 执行相同的步骤来重现错误,但没有运气!
  • 什么样的网络服务器在运行服务,在这10-15分钟的等待时间里,服务是在收到其他请求,还是服务空闲?
  • Tomcat。不,它是一个模拟服务自动取款机,除了我的电话,它什么也没有收到。
  • 我也收到此错误,但在 ResourceAccessException 的包装中。

标签: java httpclient


【解决方案1】:

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

解决这种情况的最简单方法是在一段时间不活动后从池中逐出过期连接和空闲时间超过 1 分钟的连接。详情请见this section of the HttpClient tutorial

【讨论】:

  • 我使用的是 HTTTPClient 4.5.1 ,这里两次连续重试服务器失败响应但第三次成功,所以为什么即使我保持时间 1 分钟,它也没有在第一次尝试时连接。跨度>
  • 某些连接可能有过期时间,超过该时间它们应被视为不再有效/可重复使用。空闲连接完全有效且可重复使用,但暂时未被使用。
  • 这取决于很多因素。只要服务器在方法安全性和幂等性方面符合 HTTP 规范,它应该对于安全和幂等方法是安全的。
  • 按照这里的建议使用一分钟超时正是我的问题原因:apache 客户端与本地码头服务器交谈。 AFAIK 默认情况下,码头在 30 秒不活动后关闭连接。设置connectionManager.setValidateAfterInactivity(500),即半秒解决了我的问题。我想,几秒钟也可以,但我不在乎。
  • 我们得到了这个错误,但是:我们可以看到请求到达了服务器,但是客户端显然没有得到响应,因为我们得到了NoHttpResponseException。如您所说,如果连接确实是陈旧的,那么服务器如何才能开始请求?任何人都可以解释这种情况吗?清理过时的连接会解决这个问题吗?
【解决方案2】:

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

【讨论】:

  • 鉴于原始问题指定了 POST,重试处理程序是正确的方法吗?
  • 您是在询问 POST 重试是否安全?作为设计原则,PUT 是幂等的。重试 POST 可能会产生意想不到的后果。再说一次,并不是所有的 PUT 都是幂等的。
  • 用我提供的方法重试 POST 请求几乎是完全安全的——因为它只处理和重试 NoHttpResponseException,由于客户端而不是服务器端,这更有可能发生。
  • @Jehy 这根本不是绝对安全的...... NoHttpResponseException 意味着客户端没有得到响应,而不是服务器没有得到和/或处理请求
  • 安全可以通过实现来保证,如果内容部分在密钥/身份创建过程中被重复使用,服务器可能会吐出 400 识别条目已经创建。
【解决方案3】:

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

此问题已在 HttpClient 4.4.1 中解决。见this JIRArelease notes

【讨论】:

  • 我使用的是 4.5.3 版本,但仍然出现 NoHttpResponseException。就我而言,我在每 4 次或 5 次连续请求中都会收到此异常,而不是在第一次请求中。知道我的情况可能是什么原因吗?
  • @SahilChhabra 我身边的情况相同。您找到解决问题的方法了吗?
【解决方案4】:

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

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

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

为什么?

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

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

【讨论】:

    【解决方案5】:

    解决方案:将 ReuseStrategy 更改为 never

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

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

    httpClient.setReuseStrategy(new NoConnectionReuseStrategy());
    

    同样可以在 org.apache.http.impl.client.HttpClientBuilder builder 上配置:

    builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());
    

    【讨论】:

      【解决方案6】:

      如今,大多数 HTTP 连接都是considered persistent unless declared otherwise。但是,为了节省服务器资源,连接很少会永远保持打开状态,许多服务器的默认连接超时相当短,例如 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 次连续请求中都会收到此异常,而不是在第一次请求中。知道我的情况可能是什么原因吗?
      • +1 好的,详细的答案,但是......恕我直言,这是 Apache HttpClient 错误行为的解决方法。正确的解决方案是切换到能够从陈旧连接中正常恢复的库(例如 OkHttp)。
      【解决方案7】:

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

      【讨论】:

      • 您的评论对我帮助很大。我遇到了与此相同的问题,但是使用 Spring Cloud Zuul 1.3.2.RELEASE 并且在高负载下我的解决方案是复制粘贴 org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter 并删除 disableContentCompression 调用我没有更改依赖版本,因为有相当罐子乱七八糟
      【解决方案8】:

      我在 apache http 客户端 4.5.5 上遇到同样的问题 添加默认标题

      连接:关闭

      解决问题

      【讨论】:

      • :facepalm: 这真是个坏主意。 HTTP 世界中的持久连接是出于某种目的而引入的。仅仅因为某些客户端库无法正确使用它们而放弃它们是一个巨大的错误。
      【解决方案9】:

      使用PoolingHttpClientConnectionManager 而不是BasicHttpClientConnectionManager

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

      【讨论】:

        【解决方案10】:

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

        第 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));
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多