【问题标题】:How to enable wire logging for a java HttpURLConnection traffic?如何为 java HttpURLConnection 流量启用线路日志记录?
【发布时间】:2010-11-29 13:49:55
【问题描述】:

我在另一个项目中使用了Jakarta commons HttpClient,我想要相同的wire logging 输出,但使用“标准”HttpUrlConnection。

我使用Fiddler 作为代理,但我想直接从 java 记录流量。

捕获连接输入和输出流的内容是不够的,因为 HTTP 标头是由 HttpUrlConnection 类编写和使用的,因此我将无法记录标头。

【问题讨论】:

    标签: java http logging httpurlconnection


    【解决方案1】:

    我认为您不能自动执行此操作,但您可以使用HttpUrlConnection 的输出和输入流作为参数来继承FilterOutputStreamFilterInputStream。然后在写入/读取字节时记录它们并将它们传递到底层流。

    【讨论】:

    • 我能否使用此解决方案捕获 http 标头?据我了解,您的提案只有 post 或 get 内容会通过流。
    • @Serhii 这将包括进出的所有内容,因此包括标题。
    • @Bozho:实际上,我认为它不会包含标题。当您获得流时,标头已经发送(对于输出流)或接收和解析(对于输入流)。
    【解决方案2】:

    我已经能够记录在默认流量之上实现我自己的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();
                }
            }
        }
    }
    

    【讨论】:

    • Socket.setSocketImplFactory(),对于非 SSL,也许??
    • @Serhill:一些详细的示例和/或代码摘录可能会让您接受。
    • 我认为 javaspecialist 的“SSL 流量在默认的基础上实现我自己的 SSLSocketFactory”解决方案是最好的。
    • 也使用系统属性“-Dssl.SocketFactory.provider=WireLogSSLSoketFactory” [java.sun.com/j2se/1.5.0/docs/guide/security/jsse/….
    • @Serhill:尝试使用这个类;添加了所有必要的覆盖并将 HttpsURLConnection socketFactory 设置为此类的一个实例(使用默认的 SocketFactory 实例化)。但是,在调用 HttpsURLConnection.getOutputStream() 时它不会连接,但它会使用默认的 SSLSocketFactory 进行连接。我肯定错过了什么。有什么想法吗?
    【解决方案3】:

    解决方案#1:使用装饰器模式

    您必须在HttpURLConnection 类上使用Decorator pattern 来扩展它的功能。然后覆盖所有 HttpURLConnection 方法并将操作委托给组件指针,并捕获所需的信息并记录下来。

    还要确保覆盖父类 URLConnection.getOutputStream() : OutputStreamURLConnection.html#getInputStream() : InputStream 方法以返回装饰的 OutputStreamInputStream 对象。

    .

    解决方案#2:使用自定义的内存中 http 代理

    编写一个简单的 http 代理服务器,让它在应用程序启动和初始化期间在单独的线程中启动。见Example simple proxy server

    将您的应用程序配置为对所有请求使用上述 HTTP 代理。见configuring Java to use Proxies

    现在您的所有流量都通过上述代理,就像它在 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                    |        | 
      |   |                                 |        | 
      |    ---------------------------------         | 
      |                                              | 
      ================================================ 
    

    【讨论】:

    • 查看 JDK6 源,标头通过私有方法 sun.net.www.protocol.http.HttpURLConnection.writeRequests 发送。使用修饰的 HttpURLConnection,我将能够记录方法调用和流式传输内容,但我将无法访问负责创建套接字并写入它的底层 sun.net.HttpClient 实例。
    • 添加解决方案#2:使用自定义的内存中 http 代理
    • 自定义代理无法处理 ssl。这就是我希望直接从 java 记录流量而不是使用 fiddler 的原因。
    • 对于 SSL,请参阅我的更新“使用 HTTP 代理作为基于 SSL 的 Web 服务器的适配器”。
    • 感谢您的努力。通过代理嗅探 SSL 的问题在于,为了查看未加密的内容,必须破坏证书信任链。 Fiddler 能够记录 SSL,因为它生成虚假的服务器证书。如果我直接在我们的应用程序上做同样的事情,在它被加密之前,我们不会破坏证书链。
    【解决方案4】:

    如何使用 AspectJ 插入一个切入点来围绕方法添加日志记录建议?我相信 AspectJ 可以将其融入私有/受保护的方法中。

    看来 sun.net.www.protocol.http.HttpURLConnection.writeRequest 可能会调用 sun.net.www.http.HttpClient.writeRequest ,它将 MessageHeader 对象作为输入,因此这将是您的目标。

    最终这可能会起作用,但会非常脆弱,并且只能在 Sun JVM 上运行;实际上,您只能相信您正在使用的确切版本。

    【讨论】:

    • 依赖于未发布、未记录的私有 API 是一个的想法。
    • @LawrenceDol:是的。但是,对于单个调试会话,这可能没问题。
    【解决方案5】:

    如果您只对获取网络上的内容(标题、正文等)感兴趣,您可能想试试wireshark

    这具有不必更改任何代码的优点,但如果通过代码启用日志记录是您所追求的,则此答案不适用。

    【讨论】:

      【解决方案6】:

      根据Sun's HttpURLConnection sourceJUL 提供了一些日志记录支持。

      设置(根据需要调整路径):

      -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}
      

      请注意,这只会打印没有正文的标题。

      详情请见http://www.rgagnon.com/javadetails/java-debug-HttpURLConnection-problem.html

      还有系统属性-Djavax.net.debug=all。但主要是useful for SSL debugging

      【讨论】:

      • 以编程方式,您可以使用sun.util.logging.PlatformLogger .getLogger("sun.net.www.protocol.http.HttpURLConnection") .setLevel(PlatformLogger.Level.ALL);
      • 这将引用应该已经存在的文件JRE_HOME\lib\logging.properties。如果没有看到任何输出,可能是日志级别太低,找到java.util.logging.ConsoleHandler.level 行并将其设置为FINEST
      • @user3792852:好点。我冒昧地将其添加到答案中,希望您不要介意。
      • 如果您使用的是 Tomcat:请记住,著名的 catalina.sh(或 Windows 上的 catalina.bat)确实设置了 -Djava.util.logging.config.file,它可能会覆盖您自己的命令线路选项...
      【解决方案7】:

      在 Linux 中,您可以在 strace 下运行 VM:

      strace -o strace.out -s 4096 -e trace=network -f java ...

      【讨论】:

        【解决方案8】:

        使用 Java 8 环境刷新:

        关注@sleske 的回答

        System.setProperty("javax.net.debug","all");
        

        开箱即用地为我工作。

        也是@weberjn 的建议

        strace -o strace.out -s 4096 -e trace=network -f java
        

        但在处理 SSL 流量时没有用,因为它会转储编码流。

        所有其他代码技巧都不适合我,但可能不够努力。

        【讨论】:

          猜你喜欢
          • 2020-05-23
          • 1970-01-01
          • 2018-05-06
          • 1970-01-01
          • 1970-01-01
          • 2010-11-02
          • 1970-01-01
          • 2015-08-31
          相关资源
          最近更新 更多