我在另一个项目中使用了Jakarta commons HttpClient,现在我希望使用“标准”的HttpUrlConnection来实现相同的wire logging输出。
我曾经使用过Fiddler作为代理,但我希望直接从Java中记录流量日志。
仅仅捕获连接的输入和输出流是不够的,因为HTTP头是由HttpUrlConnection类编写和消耗的,所以我将无法记录这些头部信息。
我在另一个项目中使用了Jakarta commons HttpClient,现在我希望使用“标准”的HttpUrlConnection来实现相同的wire logging输出。
我曾经使用过Fiddler作为代理,但我希望直接从Java中记录流量日志。
仅仅捕获连接的输入和输出流是不够的,因为HTTP头是由HttpUrlConnection类编写和消耗的,所以我将无法记录这些头部信息。
-Djava.util.logging.config.file=/full/path/to/logging.properties
logging.properties:
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINEST
sun.net.www.protocol.http.HttpURLConnection.level=ALL
2010-08-07 00:00:31 sun.net.www.protocol.http.HttpURLConnection writeRequests
FIN: sun.net.www.MessageHeader@16caf435 pairs: {GET /howto.html HTTP/1.1: null}{User-Agent: Java/1.6.0_20}{Host: www.rgagnon.com}{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}{Connection: keep-alive}
2010-08-07 00:00:31 sun.net.www.protocol.http.HttpURLConnection getInputStream
FIN: sun.net.www.MessageHeader@5ac0728 pairs: {null: HTTP/1.1 200 OK}{Date: Sat, 07 Aug 2010 04:00:33 GMT}{Server: Apache}{Accept-Ranges: bytes}{Content-Length: 17912}{Keep-Alive: timeout=5, max=64}{Connection: Keep-Alive}{Content-Type: text/html}
-Djavax.net.debug=all
。但它主要用于SSL调试。通过在默认的SSLSocketFactory之上实现自己的SSLSocketFactory,我已经能够记录所有SSL流量。
这对我起作用的原因是我们所有的连接都使用HTTPS,并且可以使用方法HttpsURLConnection.setSSLSocketFactory设置套接字工厂。
一种更完整的解决方案,使得所有套接字监视可行,可以在http://www.javaspecialists.eu/archive/Issue169.html找到。 感谢Lawrence Dol指出了使用Socket.setSocketImplFactory,让我朝正确的方向努力。
以下是我未准备好投入生产环境的代码:
public class WireLogSSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory delegate;
public WireLogSSLSocketFactory(SSLSocketFactory sf0) {
this.delegate = sf0;
}
public Socket createSocket(Socket s, String host, int port,
boolean autoClose) throws IOException {
return new WireLogSocket((SSLSocket) delegate.createSocket(s, host, port, autoClose));
}
/*
...
*/
private static class WireLogSocket extends SSLSocket {
private SSLSocket delegate;
public WireLogSocket(SSLSocket s) {
this.delegate = s;
}
public OutputStream getOutputStream() throws IOException {
return new LoggingOutputStream(delegate.getOutputStream());
}
/*
...
*/
private static class LoggingOutputStream extends FilterOutputStream {
private static final Logger logger = Logger.getLogger(WireLogSocket.LoggingOutputStream.class);
//I'm using a fixed charset because my app always uses the same.
private static final String CHARSET = "ISO-8859-1";
private StringBuffer sb = new StringBuffer();
public LoggingOutputStream(OutputStream out) {
super(out);
}
public void write(byte[] b, int off, int len)
throws IOException {
sb.append(new String(b, off, len, CHARSET));
logger.info("\n" + sb.toString());
out.write(b, off, len);
}
public void write(int b) throws IOException {
sb.append(b);
logger.info("\n" + sb.toString());
out.write(b);
}
public void close() throws IOException {
logger.info("\n" + sb.toString());
super.close();
}
}
}
}
解决方案#1:使用装饰器模式
您需要在HttpURLConnection类上使用装饰器模式来扩展其功能。然后覆盖所有HttpURLConnection方法,并将操作委托给组件指针,同时捕获所需信息并记录它。
还要确保覆盖父类URLConnection.getOutputStream():OutputStream和URLConnection.html#getInputStream():InputStream方法,以返回装饰的OutputStream和InputStream对象。
.
解决方案#2:使用自定义的内存http代理
编写一个简单的http代理服务器,并在应用程序启动和初始化期间在其单独的线程中启动它。请参见示例简单代理服务器。
将您的应用程序配置为使用上述HTTP代理处理所有请求。请参见配置Java以使用代理。
现在,所有流量都通过上面的代理进行,就像在fiddler中一样。因此,您可以访问原始的http流,即“从客户端到服务器”以及“从服务器返回客户端”。您需要解释这个原始信息并根据需要记录它。
更新:使用HTTP代理作为适配器连接基于SSL的Web服务器。
== Client System ===============================
| |
| ------------------------------ |
| | | |
| | Java process | |
| | ---- | |
| | ---------- | | | |
| | | | -O | | |
| | | Logging | | | |
| | | Proxy <---HTTP-- | ----- |
| | | Adapter | | | | |
| | | Thread o------------------> | |
| | | o | | | | |
| | --------|- | | Log | |
| | | | ----- |
| ----------------|------------- |
| | |
=====================|==========================
|
|
HTTPS
SSL
|
== Server System ====|==========================
| | |
| ----------------|---------------- |
| | V | |
| | | |
| | Web Server | |
| | | |
| --------------------------------- |
| |
================================================
刷新Java 8环境:
请参考@sleske的答案。
System.setProperty("javax.net.debug","all");
这个功能对我来说很容易使用。
@weberjn的建议也非常好。
strace -o strace.out -s 4096 -e trace=network -f java
所有其他代码技巧对我来说都没有起作用,但可能是我没有尝试足够努力。
使用AspectJ插入Pointcut以在方法周围添加日志建议如何?我相信AspectJ可以编织到私有/受保护的方法中。
似乎sun.net.www.protocol.http.HttpURLConnection.writeRequest可能会调用sun.net.www.http.HttpClient.writeRequest,它将MessageHeader对象作为输入,因此这将是您的目标。
最终可能会起作用,但非常脆弱,只能在Sun JVM上工作;而且您只能信任您使用的确切版本。
HttpUrlConnection
的输出和输入流作为参数传递。然后,当字节被写入/读取时,将它们记录下来并通过基础流传递。
sun.util.logging.PlatformLogger .getLogger("sun.net.www.protocol.http.HttpURLConnection") .setLevel(PlatformLogger.Level.ALL);
该代码用于设置日志记录器,在HTTP连接中记录所有级别的日志信息。 - Erich EichingerJRE_HOME\lib\logging.properties
,该文件应该已经存在。如果您没有看到任何输出,则日志级别可能太低了,请找到行java.util.logging.ConsoleHandler.level
并将其设置为FINEST
。 - user3792852