2009-09-18 41 views

回答

10

我已经能够登录默认的基础上实现我自己的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

Socket.setSocketImplFactory(),对于非SSL,可能? – 2010-02-23 04:06:15

+1

@Serhill:一些详细的例子和/或代码摘录可能会让你接受。 – 2010-02-23 04:07:24

+0

我认为javaspecialist的“SSL流量实现我自己的SSLSocketFactory的默认值”解决方案是最好的。 – 2010-02-24 02:27:43

2

我不认为你可以自动做到这一点,但你可以将FilterOutputStreamFilterInputStream的子集与HttpUrlConnection的输出和输入流作为参数一起使用。然后,当字节被写入/读取时,记录它们并将它们传递给底层流。

+0

我能捕捉到的HTTP标头用这个解决方案?根据我的理解您的提议,只有帖子或获取内容会通过流。 – Serxipc 2009-09-19 20:01:40

+0

@Serhii这将包括进出的所有东西,所以标题包括在内。 – Bozho 2010-02-21 07:48:34

+2

@Bozho:其实我不认为它会包含标题。在获取流时,头文件已经发送(用于输出流)或者接收并解析(用于输入流)。 – 2010-02-23 04:00:49

7

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

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

另外,还要确保你覆盖父类URLConnection.getOutputStream()OutputStreamURLConnection.html#getInputStream()InputStream方法返回装饰OutputStreamInputStream对象为好。

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

写一个简单的HTTP代理服务器,并将它开始在它的在应用程序启动和初始化单独的线程。见Example simple proxy server

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

现在所有的流量正在经历上面的代理,就像它在小提琴手是如何发生的。因此,您可以访问原始http流“从客户端到服务器”以及“从服务器返回客户端”。您必须解释这些原始信息并根据需要进行记录。

更新: 使用HTTP代理作为基于SSL的Web服务器的适配器。

== Client System =============================== 
    |            | 
    | ------------------------------   | 
    | |        |   | 
    | | Java process    |   | 
    | |      ---- |   | 
    | |  ---------- | | |   | 
    | |  |   | -O | |   | 
    | |  | Logging |  | |   | 
    | |  | Proxy <---HTTP-- | ----- | 
    | |  | Adapter |   | |  | | 
    | |  | Thread o------------------> | | 
    | |  |  o |   | |  | | 
    | |  --------|-   | | Log | | 
    | |    |    | ----- | 
    | ----------------|-------------   | 
    |     |       | 
    =====================|========================== 
         |       
         |       
        HTTPS       
         SSL       
         |       
    == Server System ====|========================== 
    |     |       | 
    | ----------------|----------------   | 
    | |    V    |  | 
    | |         |  | 
    | | Web Server     |  | 
    | |         |  | 
    | ---------------------------------   | 
    |            | 
    ================================================ 
+0

查看JDK6源头是在私有方法sun.net.www.protocol.http.HttpURLConnection.writeRequests中发送的。使用修饰的HttpURLConnection,我将能够记录方法调用和流内容,但是我将无法访问负责创建套接字并写入它的底层sun.net.HttpClient实例。 – Serxipc 2010-02-19 12:12:07

+0

增加解决方案#2:使用自定义的内存http代理 – 2010-02-19 16:31:53

+0

自定义代理无法处理ssl。这就是我正在寻找直接从java中记录流量而不是使用fiddler的原因。 – Serxipc 2010-02-22 18:33:57

2

怎么样使用AspectJ插入一个切入点来绕方法添加日志记录的建议?我相信AspectJ可以编入私有/受保护的方法。

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

最终这可能会工作,但会非常脆弱,只能在Sun JVM上运行;真的你只能相信你使用的确切版本。

+3

取决于未发布的,未公开的私有API是**糟糕的主意。 – 2010-02-23 04:01:47

+0

@劳伦斯代尔:确实。但是,对于单个调试会话,这可能是好的。 ' – sleske 2017-07-12 11:54:27

2

如果你只想获得线上的内容(头文件,正文等),那么你可能想要给wireshark一个提示。

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

32

根据Sun's HttpURLConnection source有一些日志支持通过JUL

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

-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: [email protected] 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: [email protected] 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

+8

程序可以使用 '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'。 – 2016-02-12 22:38:04

+1

这将; – user3792852 2016-08-30 08:51:26

+1

@ user3792852:好点。我冒昧地将其添加到答案中,希望你不介意。 – sleske 2017-07-12 12:00:00

2

在Linux中,你可以使用strace下运行VM:

strace的-o strace.out -s -e 4096一丝=网络-f java的...