我正在尝试调试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/