web-dev-qa-db-ja.com

Java.net.http.HttpClientを使用してリクエスト/レスポンスを記録する方法は?

Java 9で実験的に導入された HttpClient はJava 11で安定しましたが、驚くことではありませんが、実際に使用しているプロジェクトはほとんどないようですドキュメントはほとんど存在しません。

HTTP呼び出しを行う際に最もよくある質問の1つは、要求/応答のロギングです。もちろん、HttpClientを使用してそれをどのように行いますか?もちろん、すべての呼び出しで手動でログに記録しますか?他のすべてのHTTPクライアントによって提供されるようなインターセプターメカニズムはありますか?

11
Abhijit Sarkar

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)
...
7
Karol Dowbecki

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
8
daniel

私たちの側では、-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() {
  }

}