Apache Http 组件 - 如何使对代理的 CONNECT 请求超时?

Apache Http Components - How to timeout CONNECT request to a proxy?

不使用代理超时

我在本地启动netcat如下,基本上监听9090端口的连接:

netcat -l -p 9090

并使用 Apache HttpComponents,我创建了一个超时为 4 秒的连接..

RequestConfig requestConfig = RequestConfig.custom()
        .setSocketTimeout(4000)
        .setConnectTimeout(4000)
        .setConnectionRequestTimeout(4000)
        .build();

HttpGet httpget = new HttpGet("http://127.0.0.1:9090");
httpget.setConfig(requestConfig);

try (CloseableHttpResponse response = HttpClients.createDefault().execute(httpget)) {}

在终端(我有 netcat 运行)我看到:

??]?D???;#???9?Mۡ?NR?w?{)?V?$?(=?&?*kj?
?5??98?#?'<?%?)g@?  ?/??32?,?+?0??.?2???/??-?1???D

<!-- 4 seconds later -->
read(net): Connection reset by peer

在客户端我看到的是:

Exception in thread "main" org.apache.http.conn.ConnectTimeoutException: 
Connect to 127.0.0.1:9090 [/127.0.0.1] failed: Read timed out

这一切都在意料之中。

使用代理超时

我根据文档 here.

稍微更改了客户端代码并配置了代理
RequestConfig requestConfig = RequestConfig.custom()
         .setSocketTimeout(4000)
         .setConnectTimeout(4000)
         .setConnectionRequestTimeout(4000)
         .build();

HttpHost proxy = new HttpHost("127.0.0.1", 9090);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
CloseableHttpClient httpclient = HttpClients.custom()
        .setRoutePlanner(routePlanner)
        .build();

HttpGet httpget = new HttpGet("https://127.0.0.1:9090");
httpget.setConfig(requestConfig);

try (CloseableHttpResponse response = httpclient.execute(httpget)) {}

然后再次启动 netcat,这次是在服务器端

CONNECT 127.0.0.1:9090 HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Apache-HttpClient/4.4.1 (Java/1.8.0_212)

但超时不适用于 CONNECT。我只是永远等待..

如何将 httpclient 设置为超时 4 秒,就像我描述的第一种情况一样?

一种可能:

// This part is the same..
httpget.setConfig(requestConfig);

ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<CloseableHttpResponse> callable = () -> {
    try (CloseableHttpResponse response = httpclient.execute(httpget)) {
        return response;
    }
};

Future<CloseableHttpResponse> future = executorService.submit(callable);
try {
    future.get(4, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    httpget.abort();
    executorService.shutdownNow();
}

但我愿意接受其他建议..

RequestConfig 只有在通过特定路由完全建立到目标的连接后才会生效。它们不适用于在主消息交换之前发生的 SSL 握手或任何 CONNECT 请求。

ConnectionManager 级别配置套接字超时以确保连接级别操作在一段时间不活动后超时。