如何使用java.net.http.HttpClient记录请求/响应?

Abh*_*kar 9 java logging java-9 java-http-client java-11

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

在进行HTTP调用时,最常见的一个问题是记录请求/响应.你如何使用HttpClient,而不是在每次通话中手动记录它?是否有像所有其他HTTP客户端提供的拦截器机制?

dan*_*iel 8

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

至于测试/模拟,您可能需要查看离线测试: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
Run Code Online (Sandbox Code Playgroud)

  • jdk.httpclient.HttpClient.log的可能值是什么?您是如何找到该论据的?请详细说明您的答案。 (2认同)
  • 似乎它没有记录请求/响应主体。我想如果是 GET 请求,URL 将包含所有参数,但对于其他请求,这几乎没有用。干得好,甲骨文! (2认同)

Kar*_*cki 7

如果我们查看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

可以通过将它们设置为系统属性来启用它们。例如,运行with -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)
...
Run Code Online (Sandbox Code Playgroud)


Max*_*erc 5

在我们这边,我们没有找到-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);
  }

}
Run Code Online (Sandbox Code Playgroud)

这是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() {
  }

}
Run Code Online (Sandbox Code Playgroud)

  • “-Djdk.internal.httpclient.debug”记录器主要面向修复 HttpClient 实现中的错误的 JDK 开发人员,而不是 API 用户。我不会推荐其他人使用它。`-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel` 可能更适合 API 用户。 (2认同)