【问题标题】:How to log request/response using java.net.http.HttpClient?如何使用 java.net.http.HttpClient 记录请求/响应?
【发布时间】:2018-11-08 19:43:34
【问题描述】:

在 Java 9 中实验性引入的 HttpClient 现在在 Java 11 中已稳定,但毫不奇怪,似乎很少有项目真正使用它。文档几乎不存在。

进行 HTTP 调用时最常见的问题之一是记录请求/响应。您将如何使用HttpClient 来做到这一点,当然无需在每次通话中手动记录它?是否有类似所有其他 HTTP 客户端提供的拦截器机制?

【问题讨论】:

  • 那个 API 看起来像一场灾难。它甚至没有可模拟的界面。 (更不用说,正如您所观察到的,显然缺乏任何可通用的拦截器机制,这种机制在 20 年来一直是 HTTP 客户端的标准,有利于特殊情况的顶级接口。)
  • 很难同意“文档几乎不存在”。首先,有一个很好的 javadoc 充满了例子。其次,youtube 上有很多来自创建它的人的网络广播,例如youtube.com/watch?v=BornfFsSlc8youtube.com/watch?v=lAW_NhJ3kqs
  • @pavel 你看的肯定不是this javadoc;如果那是“好”,那么您的期望就会很低。网络广播是良好文档的一个薄弱借口。
  • @AbhijitSarkar,您可能将 javadoc 误认为是教程/指南/手册。请定义文档。
  • @pavel “有用”,又名“除了 hello world 之外的东西”怎么样?

标签: java logging java-9 java-11 java-http-client


【解决方案1】:

您可以通过在 Java 命令行上指定 -Djdk.httpclient.HttpClient.log=requests 来记录请求和响应。

至于测试/模拟,您可能想看看离线测试: 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 命令行上配置用于诊断/调试目的,具有以下值:

-Djdk.httpclient.HttpClient.log=
       errors,requests,headers,
       frames[:control:data:window:all],content,ssl,trace,channel,all

【讨论】:

  • jdk.httpclient.HttpClient.log 的可能值是多少?你是如何发现这个论点的,它是否记录在某个地方?请详细说明你的答案。
  • 附加说明设置了错误的属性,应该是-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all],content,ssl,trace ,频道
  • 似乎它没有记录请求/响应主体。我猜如果它是一个 GET 请求,URL 将包含所有参数,但对于其他人来说,这是非常没用的。干得好,甲骨文!
  • jdk11源代码中jdk.httpclient.HttpClient.log.Log.java类中提到了jdk.httpclient.HttpClient.log的可能值
  • 注意:没有记录 DNS 请求。要查看 DNS 请求,这个工具似乎很方便:genady.net/dns(我没有发现任何更容易使用的东西。如果有什么好的请添加评论)
【解决方案2】:

如果我们查看jdk.internal.net.http.common.DebugLogger 源代码,我们可以看到一些使用System.Logger 的记录器,而这些记录器又将使用System.LoggerFinder 来选择记录器框架。 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)
...

【讨论】:

  • 我也一直在看。它使用 JUL 吗?在这种情况下,我可以将它桥接到 SLF4J。
  • 这将取决于System.LoggerFinder
  • 来自 Javadoc,“当 java.logging 模块存在时,系统默认 LoggerFinder 实现使用 java.util.logging 作为后端框架。”。 java --list-modules | grep logging 显示 java.logging@11.0.1,所以我想,我的问题的答案是“是的,它使用 JUL,除非你弄乱它”。 :)
  • 我最终切换到 OkHttp,因为日志太冗长而无用。我会接受你的回答,因为它适用于我的问题。希望客户端设计允许轻松插入拦截器 - 在调试时记录所有内容或根本不记录任何内容似乎很愚蠢。
  • -Djdk.internal.httpclient.debug 记录器主要面向修复 HttpClient 实现中的错误的 JDK 开发人员,而不是 API 用户。我不会推荐其他人使用它。 -Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel 可能更适合 API 用户。
【解决方案3】:

在我们这边,我们发现-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() {
  }

}

【讨论】:

  • 我喜欢这个,但它并不完全正确。您需要通过onNext 捕获所有 传入项目,并且只在onComplete 中记录它们
  • 我很好奇为什么它不完全正确。使用上面提供的代码,在我看来,每次有数据进来时我们都会记录。另外,如果我们只登录onComplete,这是否意味着我们不会在出现错误时登录?我的印象是,它在很大程度上取决于日志记录用例,所有这些解决方案都可能有意义。编辑:错字
  • 我应该明确表达我的观点,我的错。我们正在使用多部分消息,在这种情况下,onNext 将针对 same 请求被多次调用。最终的整个请求可以在onComplete 上看到。
  • 我的理解是一样的,是的。感谢您明确表示!
  • -Djdk.internal.httpclient.debug 记录器主要面向修复 HttpClient 实现中的错误的 JDK 开发人员,而不是 API 用户。我不会推荐其他人使用它。 -Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel 可能更适合 API 用户。
猜你喜欢
  • 1970-01-01
  • 2019-01-10
  • 1970-01-01
  • 1970-01-01
  • 2014-06-15
  • 2015-10-07
  • 2019-03-16
  • 2014-12-31
  • 2016-05-01
相关资源
最近更新 更多