Как зарегистрировать запрос/ответ с помощью java.net.http.HttpClient?

HttpClient, представленный экспериментально в Java 9, теперь стабилен в Java 11, но неудивительно, что очень немногие проекты действительно используют его. Документация практически отсутствует.

Одним из наиболее распространенных запросов при совершении HTTP-вызова является регистрация запроса/ответа. Как бы вы сделали это, используя HttpClient, конечно, не регистрируя его вручную при каждом отдельном вызове? Существует ли механизм перехватчика, аналогичный тому, что предлагается всеми другими HTTP-клиентами?


person Abhijit Sarkar    schedule 08.11.2018    source источник
comment
Этот API выглядит катастрофой. У него даже нет макетного интерфейса. (Не говоря уже о том, что, как вы заметили, очевидное отсутствие какого-либо универсального механизма перехватчика, который был стандартным для HTTP-клиентов в течение 20 лет, в пользу интерфейсов верхнего уровня в специальном регистре.)   -  person chrylis -cautiouslyoptimistic-    schedule 08.11.2018
comment
Трудно согласиться с тем, что документации практически нет. Прежде всего, есть хороший javadoc, полный примеров. Во-вторых, на ютубе есть куча веб-трансляций от ребят, которые его создали, например. youtube.com/watch?v=BornfFsSlc8 или youtube.com/watch?v=lAW_NhJ3kqs   -  person pavel    schedule 13.11.2018
comment
@pavel Вы, должно быть, смотрите не на этот javadoc; если это «хорошо», ваши ожидания довольно низкие. А веб-трансляции — слабое оправдание хорошей документации.   -  person Abhijit Sarkar    schedule 13.11.2018
comment
@AbhijitSarkar, вы можете принять javadoc за учебник/руководство/руководство. Пожалуйста, определите документацию.   -  person pavel    schedule 13.11.2018
comment
@pavel как насчет «полезного», также известного как «что-то кроме привет, мир»?   -  person Abhijit Sarkar    schedule 13.11.2018


Ответы (3)


Если мы посмотрим на 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)
...
person Karol Dowbecki    schedule 08.11.2018
comment
Я тоже смотрел на это. Он использует JUL? В этом случае я могу подключить его к SLF4J. - person Abhijit Sarkar; 08.11.2018
comment
Это будет зависеть от System.LoggerFinder - person Karol Dowbecki; 08.11.2018
comment
Из Javadoc, реализация LoggerFinder по умолчанию использует java.util.logging в качестве внутренней структуры, когда присутствует модуль java.logging. java --list-modules | grep logging показывает [email protected], поэтому я думаю, что ответ на мой вопрос да, он использует JUL, если вы Это. :) - person Abhijit Sarkar; 08.11.2018
comment
В итоге я переключился на OkHttp, потому что журналы были слишком подробными, чтобы быть полезными. Я приму ваш ответ, поскольку он относится к моему вопросу. Хотелось бы, чтобы дизайн клиента позволял легко подключать перехватчики - регистрировать все при отладке или вообще ничего не кажется глупым. - person Abhijit Sarkar; 09.11.2018
comment
Регистратор -Djdk.internal.httpclient.debug в первую очередь предназначен для разработчиков JDK, которые исправляют ошибки в реализации HttpClient, а не для пользователей API. Я бы никому не рекомендовал его использовать. -Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel вероятно больше подходит для пользователей API. - person daniel; 12.01.2021

Вы можете регистрировать запросы и ответы, указав -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,all
person daniel    schedule 09.11.2018
comment
Каковы возможные значения для jdk.httpclient.HttpClient.log? Как вы узнали об этом аргументе, он где-то задокументирован? Пожалуйста, уточните в своем ответе. - person Abhijit Sarkar; 10.11.2018
comment
Дополнительное примечание устанавливает неправильное свойство, должно быть -Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all],content,ssl,trace,channel - person Mike Slinn; 08.07.2019
comment
Похоже, он не регистрирует тела запроса/ответа. Я предполагаю, что если это запрос GET, URL будет содержать все параметры, но для других это довольно бесполезно. Удачи, Оракул! - person Abhijit Sarkar; 16.07.2020
comment
Возможные значения для jdk.httpclient.HttpClient.log упоминаются в исходном коде jdk11 в классе jdk.httpclient.HttpClient.log.Log.java. - person user674669; 24.12.2020

С нашей стороны, мы не нашли журнал, предоставленный -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() {
  }

}
person Maxime Carbonneau-Leclerc    schedule 01.04.2019
comment
Мне это нравится, но это не совсем правильно. Вам необходимо записывать все входящие элементы через onNext и регистрировать их только в пределах onComplete. - person Eugene; 16.04.2020
comment
Мне интересно, почему это не совсем правильно. С приведенным выше кодом мне кажется, что мы будем логировать каждый раз, когда приходят какие-то данные. Кроме того, если мы будем логировать только onComplete, будет ли это означать, что мы не будем логироваться в случае ошибки? У меня сложилось впечатление, что это во многом зависит от варианта использования ведения журнала, и все эти решения могут иметь смысл. РЕДАКТИРОВАТЬ: опечатка - person Maxime Carbonneau-Leclerc; 17.04.2020
comment
Я должен был ясно изложить свою точку зрения, мой плохой. Мы используем сообщения, состоящие из нескольких частей, в этом случае onNext будет вызываться несколько раз для одного и того же запроса. Окончательный весь запрос можно увидеть на onComplete. - person Eugene; 17.04.2020
comment
Мое понимание такое же, да. Спасибо, что сделали это явным! - person Maxime Carbonneau-Leclerc; 17.04.2020
comment
Регистратор -Djdk.internal.httpclient.debug в первую очередь предназначен для разработчиков JDK, которые исправляют ошибки в реализации HttpClient, а не для пользователей API. Я бы никому не рекомендовал его использовать. -Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel, вероятно, больше подходит для пользователей API. - person daniel; 12.01.2021