«1. Обзор

В этом руководстве мы собираемся показать, как настроить Spring WebClient — реактивный HTTP-клиент — для регистрации запросов и ответов.

2. WebClient

WebClient is a reactive and non-blocking interface for HTTP requests, based on Spring WebFlux. It has a functional, fluent API with reactive types for declarative composition.

Behind the scenes, WebClient calls an HTTP client. Reactor Netty is the default and reactive HttpClient of Jetty is also supported. Moreover, it’s possible to plug other implementations of HTTP client by setting up a ClientConnector for WebClient.

3. Регистрация запросов и ответов

HttpClient по умолчанию, используемый WebClient, является реализацией Netty, поэтому после того, как мы изменим уровень ведения журнала Reactor.netty.http.client на DEBUG, мы можем увидеть некоторые ведение журнала запросов, но если нам нужен настраиваемый журнал, мы можем настроить наши регистраторы с помощью WebClient#filters:

WebClient
  .builder()
  .filters(exchangeFilterFunctions -> {
      exchangeFilterFunctions.add(logRequest());
      exchangeFilterFunctions.add(logResponse());
  })
  .build()

В этом фрагменте кода мы добавили два отдельных фильтра для регистрации запроса и ответа.

Давайте реализуем logRequest с помощью ExchangeFilterFunction#ofRequestProcessor:

ExchangeFilterFunction logRequest() {
    return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder("Request: \n");
            //append clientRequest method and url
            clientRequest
              .headers()
              .forEach((name, values) -> values.forEach(value -> /* append header key/value */));
            log.debug(sb.toString());
        }
        return Mono.just(clientRequest);
    });
}

logResponse — то же самое, но вместо этого мы должны использовать ExchangeFilterFunction#ofResponseProcessor.

Теперь мы можем изменить уровень журнала реактора.netty.http.client на INFO или ERROR, чтобы получить более чистый вывод.

4. Регистрация запросов и ответов с телом

HTTP-клиенты имеют функции для регистрации тел запросов и ответов. Таким образом, для достижения цели мы собираемся использовать HTTP-клиент с поддержкой журнала с нашим WebClient.

Мы можем сделать это вручную, установив WebClient.Builder#clientConnector — давайте посмотрим на HTTP-клиенты Jetty и Netty.

4.1. Ведение журнала с помощью Jetty HttpClient

Во-первых, давайте добавим зависимость Maven для jetty-reactive-httpclient в наш pom:

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-reactive-httpclient</artifactId>
    <version>1.1.6</version>
</dependency>

Затем мы собираемся создать настроенный Jetty HttpClient:

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
    @Override
    public Request newRequest(URI uri) {
        Request request = super.newRequest(uri);
        return enhance(request);
    }
};

Здесь, мы переопределили HttpClient#newRequest, а затем завернули запрос в средство расширения журнала.

Затем нам нужно зарегистрировать события с запросом, чтобы мы могли регистрировать, когда каждая часть запроса становится доступной:

Request enhance(Request request) {
    StringBuilder group = new StringBuilder();
    request.onRequestBegin(theRequest -> {
        // append request url and method to group
    });
    request.onRequestHeaders(theRequest -> {
        for (HttpField header : theRequest.getHeaders()) {
            // append request headers to group
        }
    });
    request.onRequestContent((theRequest, content) -> {
        // append content to group
    });
    request.onRequestSuccess(theRequest -> {
        log.debug(group.toString());
        group.delete(0, group.length());
    });
    group.append("\n");
    request.onResponseBegin(theResponse -> {
        // append response status to group
    });
    request.onResponseHeaders(theResponse -> {
        for (HttpField header : theResponse.getHeaders()) {
            // append response headers to group
        }
    });
    request.onResponseContent((theResponse, content) -> {
        // append content to group
    });
    request.onResponseSuccess(theResponse -> {
        log.debug(group.toString());
    });
    return request;
}

Наконец, мы должны построить экземпляр WebClient:

WebClient
  .builder()
  .clientConnector(new JettyClientHttpConnector(httpClient))
  .build()

Of Конечно, как и раньше, нам нужно установить уровень журнала RequestLogEnhancer на DEBUG.

4.2. Ведение журнала с помощью Netty HttpClient

Во-первых, давайте создадим Netty HttpClient:

HttpClient httpClient = HttpClient
  .create()
  .wiretap(true)

После включения прослушивания каждый запрос и ответ будут протоколироваться во всех подробностях.

Затем мы должны установить уровень журнала клиентского пакета Netty Reactor.netty.http.client на DEBUG:

logging.level.reactor.netty.http.client=DEBUG

Теперь давайте создадим WebClient:

WebClient
  .builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build()

Наш WebClient будет регистрировать каждый запрос и ответ во всех подробностях, но формат встроенного логгера Netty по умолчанию содержит как шестнадцатеричное, так и текстовое представление тел, а также много данных о событиях запроса и ответа.

Итак, если нам нужен только текстовый регистратор для Netty, мы можем настроить HttpClient:

HttpClient httpClient = HttpClient
  .create()
  .wiretap("reactor.netty.http.client.HttpClient", 
    LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL);

5. Заключение

В этом руководстве мы использовали несколько методов для регистрации данных запросов и ответов во время с помощью Spring WebClient.

Как всегда, код доступен на GitHub.