我正在尝试调试http客户端请求。我需要获取连接时使用的本地端口号。我可以使用HttpClientContext来获取连接,并在连接成功时检索本地端口。但是,在抛出IOExceptions的情况下,检索本地端口号时会出现ConnectionShutdownException。有关如何在出错时获取所有http请求的本地端口号的任何线索。
答案 0 :(得分:2)
这适用于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,因为你不能简单地在这种情况下实例化套接字,你必须这样做:
return (SSLSocket) this.javaxNetSSLSocketFactory.createSocket();
在这种情况下,您无法创建匿名子类。而且由于Socket也没有接口,你甚至无法代理它来装饰它。所以你可能(uglyest代码)创建一个包装和委托给SSLSocket的Socket子类,但那是绝望的。
所以,回顾一下。
如果我们只关心某些连接到服务器的套接字,那么解决方案就相当简单了。
我们构建的方案注册表用于覆盖ConnectionManager
的自定义ConnectionOperator
。运算符的覆盖方法是prepareSocket
,它允许我们只使用端口信息更新我们发送的任何请求的HttpContext
。当一切顺利时,这是一种查找信息的简便方法。
如果我们想关心归因于一个永远不会连接的套接字的本地端口(我认为它的用途有限),需要更深入。
我们自己的SchemeRegistry
应设计为自定义SocketFactories
。这些应该是修饰符或默认值... createSocket
覆盖方法允许我们拦截&#34;本地端口上的绑定(并将其存储为ConcurrentMap<Socket, Integer>
或ThreadLocal
),并覆盖&#39; connectSocket&#39;我们捕获可能发生的任何异常,并重新抛出它,但不是在将它包装在我们自己的异常类型中之前,它可以保存本地端口信息。这样,如果在调用客户端时异常通过,通过检查原因链,您将找到您的端口数据。如果没有异常发生,我们清理或映射实例/线程本地。