java - Apache InternalHttpClient:确定本地端口号

标签 java apache-httpclient-4.x

我正在尝试调试http客户端请求。我需要获取建立连接时使用的本地端口号。我可以使用HttpClientContext获取连接,并在连接成功时检索本地端口。但是,在引发IOExceptions的情况下,检索本地端口号时会收到ConnectionShutdownException。关于如何在发生错误的情况下如何获取所有http请求的本地端口号的任何线索。

最佳答案

这用于HTTPClient 4.0.1(我拥有的最新版本)。
我没有找到任何简单的班轮...

毫无疑问,http客户端实际上将套接字绑定到本地主机/端口并连接到远程主机/端口的部分是SocketFactory。在HTTPClient中,套接字工厂与SchemeRegistry相关联,而SchemeRegistry又属于连接管理器。请注意,HTTPClient的SocketFactory不是javax.net.SocketFactory,而是此类对象的包装器。您可以这样定义一个方案:

SchemeRegistry schRgstr = new SchemeRegistry();
Scheme httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
schRgstr.register(httpScheme);


当然,org.apache.http.conn.scheme.SocketFactory是一个接口,因此您可以修饰它以完成您想要的任何事情,这将派上用场。

httpclient调用套接字工厂的部分称为ClientConnectionOperator(这也是一个接口)。实际上,该对象也绑定到连接管理器,而不是客户端本身。因此,如果您想自定义连接操作符,则也可以覆盖连接管理器,例如,像这样(匿名类):

ThreadSafeClientConnManager connMngr = new ThreadSafeClientConnManager(httpParams, schRgstr) {
  protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
    return new YourConnectionOperator(schreg);
  };
};


套接字的生命周期模型如下所示:


当连接管理器需要立即连接时,它将在运算符上调用createConnection(基本上什么都不做,只是创建一个内部对象,该对象最终将保存实际的套接字)。
进一步,它调用openConnection
openConnection转到SocketFactory并请求一个新的Socket,然后尝试这样连接它(此处sf是“ httpclient套接字工厂”)

sock = sf.connectSocket(sock, target.getHostName(),
      schm.resolvePort(target.getPort()),
      local, 0, params);

如果此调用失败,则会引发异常,并且将无法访问更多信息。我们将回到这一点。
但是,如果connectSocket有效,则会在连接运算符上调用prepareSocket方法。因此,您可以覆盖该方法并将端口信息放入上下文中(或您喜欢的其他任何东西):

@Override
protected void prepareSocket(Socket sock, HttpContext context, HttpParams params) throws IOException {
  super.prepareSocket(sock, context, params);
  context.setAttribute("LOCAL PORT INTERCEPTOR", sock.getLocalPort());
}



调用HTTPClient时会传递使用的HttpContext实例,因此即使由于某些其他异常而导致调用以后失败,您也可以访问它。拨打电话时,请这样:

HttpContext ctx = new BasicHttpContext();
HttpGet get = new HttpGet(targetUrl.getUrl());
HttpResponse resp = client.execute(get, ctx);


在此代码中,如果客户端可以执行第5步,那么即使以后发生异常(连接丢失,超时,无效的HTTP等),您也可以访问端口信息。

更进一步

所有这一切中仍然存在一个暗区:如果在第4步调用失败(实际打开套接字,例如在解析目标主机名时遇到DNS错误)...我不确定这种情况是否确实存在有趣的是,(我看不出为什么会这样)。看到处理它开始变得“​​混乱”,您应该真正考虑是否需要它。

为此,我们需要开始进行非常深入的覆盖,这涉及很多工作-我认为其中的一些不是很好的设计。

之所以会出现复杂性,是因为HTTPClient的作者没有提供一种可以覆盖以获取所需信息的必要方法。在套接字工厂内部,有趣的一点是:

sock = createSocket();
InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);
sock.bind(isa);
// go on and connect to the server
// like socket.connect...


没有方法可以将套接字打开的本地端和服务器端分开,因此,如果在服务器端抛出任何套接字异常,则对套接字实例的访问将丢失,并且本地端口信息也将随之消失。

但是,一切并不会丢失,因为我们确实可以使用一个入口点:createSocket方法!默认实现是

public Socket createSocket() {
    return new Socket();
}


但是,因为Socket并非最终课程,所以您可以...玩!

public Socket createSocket() {
    return new Socket() {
      @Override
      public void bind(SocketAddress bindpoint) throws IOException {
         super.bind(bindpoint);
         // get the local port and give the info back to whomever you like
      }
    };
}


问题是:这适用于普通套接字(因为我们可以创建一个匿名子类),但是不适用于HTTPS,因为在这种情况下您不能简单地实例化一个Socket,所以必须这样做:

return (SSLSocket) this.javaxNetSSLSocketFactory.createSocket();


在这种情况下,您将无法创建匿名子类。而且由于Socket也不是接口,因此您甚至无法代理它来装饰它。因此,您可以(有史以来最丑陋的代码)创建一个Socket子类,该子类包装并委托给SSLSocket,但那是绝望的。

因此,回顾一下时间。

如果我们只关心在某些时候连接到服务器的套接字,则解决方案非常简单。

我们构建的方案注册表用于覆盖ConnectionManager的自定义ConnectionOperator中。运算符的覆盖方法是prepareSocket,它使我们能够简单地使用端口信息更新发送的任何请求的HttpContext。当一切顺利时,这是一种查找信息的简便方法。

如果我们想关心归因于从未连接过的套接字的本地端口(我认为这是有限用途的),则需要更深入地研究。

我们自己的SchemeRegistry应该设计为具有自定义SocketFactories。这些可能应该是装饰器或默认的装饰器。createSocket重写的方法允许我们“拦截”本地端口上的绑定(并将其存储到ConcurrentMap<Socket, Integer>ThreadLocal中),并通过重写我们使用'connectSocket'捕获可能发生的任何异常,然后将其重新抛出,但在将其包装到可以保存本地端口信息的异常类型之前,不可以。这样,如果在调用客户端时异常通过了,则通过检查原因链,您将找到端口数据。如果没有异常发生,我们将在本地清理或映射实例/线程。

关于java - Apache InternalHttpClient:确定本地端口号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23352764/

相关文章:

java - 如何在Struts2中返回HTTP错误状态码*和*内容

java - Apache HTTP 客户端,来自特定网络接口(interface)的请求

apache-httpclient-4.x - 如何使用 Apache HTTPComponents HttpClient 在 HTTP 请求中启用 SNI?

java - 我想使用 HttpClient4 对并发发送请求进行 RESTful API 压力测试,我可以使用一个 HttpClient 实例吗?

java - 从字符串创建 URL

Java - 如何阻止用户输入整数以外的内容?

java - SSL 客户端 - 什么时候需要证书?

java - Maven 项目中的多个 web.xml 数据库连接

apache-httpclient-4.x - Apache HTTP 客户端不遵守 HTTP 状态代码 307

apache-httpclient-4.x - 无需解压即可从 Apache-httpclient 中提取 gzip 数据