Apache HTTPClient5 - 如何防止 Connection/Stream 被拒绝

Apache HTTPClient5 - How to Prevent Connection/Stream Refused

问题陈述

上下文

直接问题

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

            });
        }

直接提问

已修复,结合以下内容以确保连接 Live/Ready

(或者至少是稳定的)

强制 HTTP 1

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

为 POST

设置有效 Headers
  • 特别是收盘价header
    • req.setHeader("Connection", "close, TE");
    • 注意:闲置检查有帮助,但有时仍会遭到拒绝 w/o this

按类型设置不活动检查

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

给定错误上下文

  • 跟踪堆栈跟踪中的连接问题到 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;

推理

  • 如果我没理解错的话:
    • 连接正在 un/intentionally 关闭
      • 但是,在再次执行之前没有确认它们准备就绪
      • 这导致它失败,因为流不可行
    • 因此修复有效,因为(看起来)
      • 给定强制 HTTP1 允许管理单个上下文
        • 其中 HttpVersionPolicy NEGOTIATE/FORCE_HTTP_2 在 regions/menus
        • 范围内有更大或同等的失败
      • 并确保所有连接在使用前都有效
      • 而POST由于关闭header而一直关闭,这对于HTTP2
      • 是不可用的
      • 因此
        • GET 以合理的周期检查有效性
        • 每次都检查
        • POST,由于是强行关闭,所以在执行前是re-acquired
        • 这没有为意外关闭留下空间
          • 否则可能会错误地切换到 HTTP2

会接受这个,直到出现更好的答案,因为这是稳定的,但 sub-optimal。