Как включить протоколирование проводов для трафика Java HttpURLConnection?
Я использовал Jakarta commons HttpClient в другом проекте, и я хотел бы получить тот же вывод журнала проводов, но с использованием "стандартного" HttpUrlConnection.
Я использовал Fiddler в качестве прокси, но я хотел бы регистрировать трафик непосредственно из Java.
Захват потоков входного и выходного соединений недостаточен, поскольку заголовки HTTP записываются и используются классом HttpUrlConnection, поэтому я не смогу регистрировать заголовки.
8 ответов
Я смог зарегистрировать весь трафик SSL, реализуя свой собственный SSLSocketFactory поверх стандартного.
Это сработало для меня, потому что все наши соединения используют 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();
}
}
}
}
Согласно источнику Sun HttpURLConnection, есть некоторая поддержка регистрации через 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: 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
, Но это в основном полезно для отладки SSL.
Решение № 1: Используйте шаблон Decorator
Вам придется использовать шаблон Decorator в классе HttpURLConnection, чтобы расширить его функциональность. Затем переопределите все методы HttpURLConnection и делегируйте операцию указателю компонента, а также соберите необходимую информацию и зарегистрируйте ее.
Также убедитесь, что вы переопределяете родительский класс URLConnection.getOutputStream (): OutputStream и URLConnection.html # getInputStream (): методы InputStream также возвращают оформленные объекты OutputStream и InputStream.
,
Решение №2. Использование настраиваемого http-прокси в памяти
Напишите простой http прокси-сервер и запустите его в отдельном потоке во время запуска и инициализации приложения. Смотрите Пример простого прокси-сервера.
Настройте ваше приложение на использование указанного выше HTTP-прокси для всех ваших запросов. См. Настройка Java для использования прокси.
Теперь весь ваш трафик проходит через прокси, как и в fiddler. Следовательно, у вас есть доступ к сырому потоку http "от клиента к серверу", а также "обратно с сервера на клиент". Вам придется интерпретировать эту необработанную информацию и регистрировать ее по мере необходимости.
Обновление: используйте HTTP-прокси в качестве адаптера для веб-сервера на основе SSL.
== 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
но бесполезно, если обрабатывается трафик SSL, поскольку он создает дамп закодированного потока.
Все остальные трюки с кодом у меня не сработали, но, возможно, не слишком старались.
В Linux вы можете запустить виртуальную машину под strace:
strace -o strace.out -s 4096 -e трассировка = сеть -f Java ...
Я не думаю, что вы можете сделать это автоматически, но вы могли бы создать подкласс FilterOutputStream и FilterInputStream с HttpUrlConnection
выходной и входной потоки в качестве параметров. Затем, когда байты записываются / читаются, регистрируйте их, а также передавайте их нижележащим потокам.
Как насчет использования AspectJ для вставки Pointcut для добавления рекомендаций по ведению журнала вокруг метода? Я полагаю, что AspectJ может проложить себе путь к закрытым / защищенным методам.
Похоже, что sun.net.www.protocol.http.HttpURLConnection.writeRequest может вызывать sun.net.www.http.HttpClient.writeRequest, который принимает объект MessageHeader в качестве входного, чтобы он был вашей целью.
В конце концов это может сработать, но будет ужасно хрупким и работать только на Sun JVM; и действительно, вы можете доверять только той версии, которую используете.
Если вы заинтересованы только в том, чтобы получить доступ к содержимому канала (заголовкам, телу и т. Д.), Вы можете попробовать использовать Wireshark.
Преимущество этого в том, что вам не нужно менять какой-либо код, хотя, если вы хотите включить регистрацию с помощью кода, этот ответ неприменим.