【问题标题】:How can I get the underlying Socket from an httpclient connection?如何从 httpclient 连接获取底层 Socket?
【发布时间】:2021-04-23 01:51:41
【问题描述】:

这个问题的措辞与 SO (HttpClient: how do I obtain the underlying socket from an existing connection?) 上的另一个问题的措辞相同,但这个问题实际上是关于通过 HTTP(S) 为其他协议建立隧道,因此答案有很大不同。

我要做的是建立一个 HTTPS 连接,然后找出该连接的详细信息。 Java 的 SSLSocket 类会给我我需要的东西,但我需要能够掌握 Socket 本身才能查询它。

有没有办法到达底层Socket? httpclient/httpcore 已经变成了工厂和私有/受保护的事物实现的迷宫,因此很难在 API 周围探索一下,以便在配置完成后弄清楚如何实际获取它们。

【问题讨论】:

  • 你到底要从套接字中寻找什么信息?如果HttpClient 没有公开对其Socket 对象的访问权限,您可以直接使用(SSL)Socket 手动实现HTTP(S)。
  • 你不能。公开创建的套接字的唯一方法是提供自己的套接字工厂并自己存储对创建的套接字的引用
  • @RemyLebeau 我正在寻找协商的 TLS 协议和密码套件。我已经限制了那些可以协商的内容,但我想记录那些实际选择用于对话的内容。
  • @Antoniossss 你当然可以直接使用(SSL)Socket,即使你是从工厂获得的。没有什么说您必须使用HttpClient 来实现 HTTP(S)。这是一种方便,而不是要求。如果您了解 TCP 和 HTTP 的工作原理,并且掌握了足够的技能,您可以直接在自己的代码中实现 HTTP(S),而无需使用HttpClient。我不会推荐它,但它是可行的。
  • @RemyLebeau 老实说,我现在真的不喜欢HttpClient。我在我们正在使用的服务的一些示例代码中容忍了它,但我将把它全部扔到窗外并自己重新实现 HTTP 的东西。我们已经为其他服务编写了自己的 HTTP 客户端,这非常简单。我们不需要 1000 个类和接口来处理 HTTP POST 和 HTTP GET。

标签: java sockets apache-httpclient-4.x


【解决方案1】:

HttpClient 故意使获取底层连接对象和它所绑定的套接字变得困难,主要是为了确保连接状态是一致的,并且连接池中的持久连接可以安全地被另一个事务重用。

但是,可以从响应拦截器中获取底层连接。

CloseableHttpClient httpclient = HttpClients.custom()
        .addInterceptorLast(new HttpResponseInterceptor() {
            @Override
            public void process(
                    final HttpResponse response,
                    final HttpContext context) throws HttpException, IOException {
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                ManagedHttpClientConnection connection = clientContext.getConnection(ManagedHttpClientConnection.class);
                // Can be null if the response encloses no content
                if (connection != null) {
                    Socket socket = connection.getSocket();
                    System.out.println(socket);
                }

            }
        })
        .build();
try (CloseableHttpResponse response = httpclient.execute(new HttpGet("http://www.google.com/"))) {
    System.out.println(response.getStatusLine());
    EntityUtils.consume(response.getEntity());
}

【讨论】:

  • 获得Socket 后,您可以将其类型转换为SSLSocket 并获取它的SSLSession,然后它可以告诉您协商的SSL 协议和正在使用的密码套件。跨度>
  • 我现在正在尝试。当我输入代码时,我突然想到这个拦截器可能会为我得到的每个响应触发......所以如果我使用 HTTP keep-alive 并且连接保持打开状态,我的拦截器将为每个请求触发,而不仅仅是在TLS 握手完成。我在编写代码时必须考虑到这一点,以避免多次记录 TLS 信息。
  • @RemyLebeau 是的,我知道如何将套接字投射到SSLSocket 并获取协议参数。我只是不知道如何获取套接字本身。
  • 因为我只对最初的握手感兴趣,所以我稍微修改了这段代码。我没有使用HttpResponseInterceptor,而是创建了一个新的HttpClientContext,并用它来发出我的第一个请求(这始终是一个“验证”请求,所以总是第一个)。 process 方法中的所有内容在此之后仍然适用。
【解决方案2】:

我最终使用了一种不同的技术,但@oleg 让我走上了正确的道路。这是我的一次性代码:

HttpClientContext ctx = HttpClientContext.create();
HttpResponse response = getHttpClient().execute(method, ctx);

if(log.isDebugEnabled())
{
    ManagedHttpClientConnection connection = ctx.getConnection(ManagedHttpClientConnection.class);
    // Can be null if the response encloses no content
    if(null != connection)
    {
        Socket socket = connection.getSocket();
        if(socket instanceof SSLSocket)
        {
            SSLSocket sslSock = (SSLSocket)socket;
            log.debug("Connected to " + getEndpointURL()
                      + " using " + sslSock.getSession().getProtocol()
                      + " and suite " + sslSock.getSession().getCipherSuite());
        }
    }
}

【讨论】:

    猜你喜欢
    • 2011-04-14
    • 2011-06-02
    • 1970-01-01
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    • 2015-12-02
    • 2020-12-15
    • 1970-01-01
    相关资源
    最近更新 更多