首页 > 解决方案 > Apache HTTPClient5 - 如何防止连接/流被拒绝

问题描述

问题陈述

语境

直接问题

if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
    throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}

解决问题的一些尝试

PoolingAsyncClientConnectionManagerBuilder builder = PoolingAsyncClientConnectionManagerBuilder
        .create()
        .setTlsStrategy(getTlsStrategy())
        .setMaxConnPerRoute(12)
        .setMaxConnTotal(12)
        .setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))
        .setConnectionTimeToLive(TimeValue.ofMinutes(2))
        .build();
private HttpClientContext getHttpClientContext() {
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(Timeout.of(10, TimeUnit.SECONDS))
            .setResponseTimeout(Timeout.of(10, TimeUnit.SECONDS))
            .build();

    HttpClientContext httpContext = HttpClientContext.create();
    httpContext.setRequestConfig(requestConfig);
    return httpContext;
}

用于分析的初始代码段

(除了上述带更改尝试的部分)

public SimpleHttpResponse getFullResponse(String url, PoolingAsyncClientConnectionManager manager, SimpleHttpRequest req) {
            try (CloseableHttpAsyncClient httpclient = getHTTPClientInstance(manager)) {
                httpclient.start();

                CountDownLatch latch = new CountDownLatch(1);
                long startTime = System.currentTimeMillis();
                Future<SimpleHttpResponse> future = getHTTPResponse(url, httpclient, latch, startTime, req);

                latch.await();
                return future.get();
            } catch (IOException | InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return new SimpleHttpResponse(999, CommonUtils.getExceptionAsMap(e).toString());
            }
        }
private Future<SimpleHttpResponse> getHTTPResponse(String url, CloseableHttpAsyncClient httpclient, CountDownLatch latch, long startTime, SimpleHttpRequest req) {
            return httpclient.execute(req, getHttpContext(), new FutureCallback<SimpleHttpResponse>() {

                @Override
                public void completed(SimpleHttpResponse response) {
                    latch.countDown();
                    logger.info("[{}][{}ms] - {}", response.getCode(), getTotalTime(startTime), url);
                }

                @Override
                public void failed(Exception e) {
                    latch.countDown();
                    logger.error("[{}ms] - {} - {}", getTotalTime(startTime), url, e);
                }

                @Override
                public void cancelled() {
                    latch.countDown();
                    logger.error("[{}ms] - request cancelled for {}", getTotalTime(startTime), url);
                }

            });
        }

直接问题

标签: apache-httpclient-5.x

解决方案


修复了以下内容的组合以确保连接实时/就绪

(或者至少是稳定的)

强制 HTTP 1

HttpAsyncClients.custom()
    .setConnectionManager(manager)
    .setRetryStrategy(getRetryStrategy())
    .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
    .setConnectionManagerShared(true);

为 POST 设置有效标头

  • 特别是关闭标题
    • req.setHeader("Connection", "close, TE");
    • 注意:不活动检查有帮助,但有时仍会被拒绝

按类型设置不活动检查

  • 将 POST 设置为在不活动后立即验证
    • 注意:两者都使用 1000 会导致某些系统的掉率较高
PoolingAsyncClientConnectionManagerBuilder
    .create()
    .setValidateAfterInactivity(TimeValue.ofMilliseconds(0))
  • 将 GET 设置为 1 秒后验证
PoolingAsyncClientConnectionManagerBuilder
    .create()
    .setValidateAfterInactivity(TimeValue.ofMilliseconds(1000))

给定错误上下文

  • 将stacktrace中的连接问题跟踪到AbstractH2StreamMultiplexer
  • 显示 ConnectionHandshake.GRACEFUL_SHUTDOWN 触发流拒绝
 if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
    throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
}
  • 对应于
connState = streamMap.isEmpty() ? ConnectionHandshake.SHUTDOWN : ConnectionHandshake.GRACEFUL_SHUTDOWN;

推理

  • 如果我理解正确:
    • 连接被非/有意关闭
      • 但是,在再次执行之前,它们并没有被确认准备好
      • 这导致它失败,因为流不可行
    • 因此修复有效,因为(似乎)
      • 鉴于强制 HTTP1 允许管理单个上下文
        • HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在区域/菜单的范围内有更大或等效的故障
      • 并确保所有连接在使用前都是有效的
      • 并且由于 HTTP2 不可用的 close 标头,POST 总是关闭
      • 所以
        • 使用合理的周期性检查 GET 的有效性
        • 每次都检查POST,由于是强制关闭,所以在执行前重新获取
        • 这没有为意外关闭留下空间
          • 否则可能会错误地切换到 HTTP2

将接受这一点,直到出现更好的答案,因为这是稳定但次优的。


推荐阅读