如何使用 java.net.http.HttpClient 登录 request/response?
How to log request/response using java.net.http.HttpClient?
在Java 9 中试验引入的HttpClient 现在在Java 11 中稳定了,但不出所料,似乎很少有项目真正使用它。文档几乎不存在。
进行 HTTP 调用时最常见的问题之一是记录 request/response。您将如何使用 HttpClient
做到这一点,当然不用在每次调用中手动记录它?是否有所有其他 HTTP 客户端提供的拦截器机制?
如果我们查看 jdk.internal.net.http.common.DebugLogger
源代码,我们可以看到一些记录器使用 System.Logger
, which in turn will useSystem.LoggerFinder
到 select 记录器框架。 JUL 是默认选择。记录器名称是:
- jdk.internal.httpclient.debug
- jdk.internal.httpclient.websocket.debug
- jdk.internal.httpclient.hpack.debug
可以通过将它们设置为系统来启用它们 属性。例如 运行 -Djdk.internal.httpclient.debug=true
将产生:
DEBUG: [main] [147ms] HttpClientImpl(1) proxySelector is sun.net.spi.DefaultProxySelector@6dde5c8c (user-supplied=false)
DEBUG: [main] [183ms] HttpClientImpl(1) ClientImpl (async) send https://http2.github.io/ GET
DEBUG: [main] [189ms] Exchange establishing exchange for https://http2.github.io/ GET,
proxy=null
DEBUG: [main] [227ms] PlainHttpConnection(?) Initial receive buffer size is: 43690
DEBUG: [main] [237ms] PlainHttpConnection(SocketTube(1)) registering connect event
DEBUG: [HttpClient-1-SelectorManager] [239ms] SelectorAttachment Registering jdk.internal.net.http.PlainHttpConnection$ConnectEvent@354bf356 for 8 (true)
...
您可以通过在 Java 命令行上指定 -Djdk.httpclient.HttpClient.log=requests
来记录请求和响应。
至于testing/mocking你可能想看看离线测试:
http://hg.openjdk.java.net/jdk/jdk/file/tip/test/jdk/java/net/httpclient/offline/
根据您希望实现的目标,您也可以使用“DelegatingHttpClient”来拦截和记录请求和响应。
除了 Java API 文档之外,http://openjdk.java.net/groups/net/httpclient/index.html
上还有一些高级文档
补充说明:
jdk.httpclient.HttpClient.log
属性 是一个特定的实现 属性 其值是一个逗号分隔的列表,可以在 Java 命令行上为 [=28= 配置] 具有以下值的目的:
-Djdk.httpclient.HttpClient.log=
errors,requests,headers,
frames[:control:data:window:all],content,ssl,trace,channel,all
在我们这边,我们发现 -Djdk.internal.httpclient.debug
提供的日志记录不够可读。我们想出的解决方案是用一个装饰器包装 HttpClient,该装饰器将能够拦截调用并提供日志记录。这是它看起来的样子(不仅应该为 send
但 sendAsync
方法完成):
public class HttpClientLoggingDecorator extends HttpClient {
private static final Logger logger = Logger.getLogger(HttpClientLoggingDecorator.class.getName());
private final HttpClient client;
...
@Override
public <T> HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)
throws IOException,
InterruptedException
{
subscribeLoggerToRequest(req);
HttpResponse<T> response = client.send(req, responseBodyHandler);
logResponse(response);
return response;
}
private void subscribeLoggerToRequest(HttpRequest req) {
// define a consumer for how you want to log
// Consumer<String> bodyConsumer = ...;
if (req.bodyPublisher().isPresent()) {
req.bodyPublisher()
.ifPresent(bodyPublisher -> bodyPublisher.subscribe(new HttpBodySubscriber(bodyConsumer)));
} else {
bodyConsumer.accept(NO_REQUEST_BODY);
}
}
private <T> void logResponse(HttpResponse<T> response) {
// String responseLog = ...;
logger.info(responseLog);
}
}
这里是 HttpBodySubscriber
:
public class HttpBodySubscriber implements Flow.Subscriber<ByteBuffer> {
private static final long UNBOUNDED = Long.MAX_VALUE;
private final Consumer<String> logger;
public HttpBodySubscriber(Consumer<String> logger) {
this.logger = logger;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
subscription.request(UNBOUNDED);
}
@Override
public void onNext(ByteBuffer item) {
logger.accept(new String(item.array(), StandardCharsets.UTF_8));
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
}
在Java 9 中试验引入的HttpClient 现在在Java 11 中稳定了,但不出所料,似乎很少有项目真正使用它。文档几乎不存在。
进行 HTTP 调用时最常见的问题之一是记录 request/response。您将如何使用 HttpClient
做到这一点,当然不用在每次调用中手动记录它?是否有所有其他 HTTP 客户端提供的拦截器机制?
如果我们查看 jdk.internal.net.http.common.DebugLogger
源代码,我们可以看到一些记录器使用 System.Logger
, which in turn will useSystem.LoggerFinder
到 select 记录器框架。 JUL 是默认选择。记录器名称是:
- jdk.internal.httpclient.debug
- jdk.internal.httpclient.websocket.debug
- jdk.internal.httpclient.hpack.debug
可以通过将它们设置为系统来启用它们 属性。例如 运行 -Djdk.internal.httpclient.debug=true
将产生:
DEBUG: [main] [147ms] HttpClientImpl(1) proxySelector is sun.net.spi.DefaultProxySelector@6dde5c8c (user-supplied=false)
DEBUG: [main] [183ms] HttpClientImpl(1) ClientImpl (async) send https://http2.github.io/ GET
DEBUG: [main] [189ms] Exchange establishing exchange for https://http2.github.io/ GET,
proxy=null
DEBUG: [main] [227ms] PlainHttpConnection(?) Initial receive buffer size is: 43690
DEBUG: [main] [237ms] PlainHttpConnection(SocketTube(1)) registering connect event
DEBUG: [HttpClient-1-SelectorManager] [239ms] SelectorAttachment Registering jdk.internal.net.http.PlainHttpConnection$ConnectEvent@354bf356 for 8 (true)
...
您可以通过在 Java 命令行上指定 -Djdk.httpclient.HttpClient.log=requests
来记录请求和响应。
至于testing/mocking你可能想看看离线测试: http://hg.openjdk.java.net/jdk/jdk/file/tip/test/jdk/java/net/httpclient/offline/
根据您希望实现的目标,您也可以使用“DelegatingHttpClient”来拦截和记录请求和响应。
除了 Java API 文档之外,http://openjdk.java.net/groups/net/httpclient/index.html
上还有一些高级文档补充说明:
jdk.httpclient.HttpClient.log
属性 是一个特定的实现 属性 其值是一个逗号分隔的列表,可以在 Java 命令行上为 [=28= 配置] 具有以下值的目的:
-Djdk.httpclient.HttpClient.log=
errors,requests,headers,
frames[:control:data:window:all],content,ssl,trace,channel,all
在我们这边,我们发现 -Djdk.internal.httpclient.debug
提供的日志记录不够可读。我们想出的解决方案是用一个装饰器包装 HttpClient,该装饰器将能够拦截调用并提供日志记录。这是它看起来的样子(不仅应该为 send
但 sendAsync
方法完成):
public class HttpClientLoggingDecorator extends HttpClient {
private static final Logger logger = Logger.getLogger(HttpClientLoggingDecorator.class.getName());
private final HttpClient client;
...
@Override
public <T> HttpResponse<T> send(HttpRequest req, HttpResponse.BodyHandler<T> responseBodyHandler)
throws IOException,
InterruptedException
{
subscribeLoggerToRequest(req);
HttpResponse<T> response = client.send(req, responseBodyHandler);
logResponse(response);
return response;
}
private void subscribeLoggerToRequest(HttpRequest req) {
// define a consumer for how you want to log
// Consumer<String> bodyConsumer = ...;
if (req.bodyPublisher().isPresent()) {
req.bodyPublisher()
.ifPresent(bodyPublisher -> bodyPublisher.subscribe(new HttpBodySubscriber(bodyConsumer)));
} else {
bodyConsumer.accept(NO_REQUEST_BODY);
}
}
private <T> void logResponse(HttpResponse<T> response) {
// String responseLog = ...;
logger.info(responseLog);
}
}
这里是 HttpBodySubscriber
:
public class HttpBodySubscriber implements Flow.Subscriber<ByteBuffer> {
private static final long UNBOUNDED = Long.MAX_VALUE;
private final Consumer<String> logger;
public HttpBodySubscriber(Consumer<String> logger) {
this.logger = logger;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
subscription.request(UNBOUNDED);
}
@Override
public void onNext(ByteBuffer item) {
logger.accept(new String(item.array(), StandardCharsets.UTF_8));
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
}
}