Рубрики
Без рубрики

Регистрация тела запроса с помощью веб-клиента Spring

О веб-клиенте Spring Весна становится реактивной. Вам нужно только один раз взглянуть на тал… Помечено как spring, java, веб-клиент, ведение журнала.

Весна становится реактивной. Вам достаточно одного взгляда на доклады, представленные на конференции Spring One 2020, чтобы увидеть, что парадигмы реактивного веб- и функционального программирования в Java и Spring были на переднем крае.

Одна из интересных проблем, связанных с выполнением действий неблокирующим способом, заключается в том, что такие простые вещи, как ведение журнала, иногда могут немного усложняться. Поскольку вы не знаете точно, КОГДА данные будут доступны, вы не можете просто поместить их в журнал так же, как вы бы сделали это с помощью чего-то вроде Spring RestTemplate. ((Кстати, о шаблоне RestTemplate, похоже, он предназначен для амортизации! https://docs.spring.io/spring-framework/docs/current/javadoc-api/index.html?org/springframework/web/client/RestTemplate.html )

Согласно документации, мы должны использовать org.springframework.web.реактивный.клиент. WebClient для наших исходящих вызовов API в настоящее время, особенно потому, что он предоставляет нам возможность использовать блокирующие и неблокирующие методы.

Теперь любой, кто использовал веб-клиент Spring, может подтвердить тот факт, что извлечение содержимого запроса или содержимого ответа иногда может быть немного затруднено, особенно если вы ищете определенный формат.

Есть десятки неотвеченных сообщений о переполнении стека, которые имеют один и тот же ответ, ознакомьтесь со статьей Баельдунга на эту тему: https://www.baeldung.com/spring-log-webclient-calls .

Теперь Баэлдунг спасал мою задницу больше раз, чем я могу сосчитать, в побочных проектах И профессионально. Однако в статье показано не более чем базовые примеры реализации. Чего не хватает, так это вывода примера и обмена предостережениями, которые не упоминаются в статье.

Итак, без лишних слов, вот пошаговое руководство по наилучшему методу (на мой взгляд) ведения журнала запросов и ответов (с телом HTTP) в Spring Webclient, с примерами, комментариями и выводами.

Ведение журнала Netty включено в сообщение Baeldung, но оно и близко не такое детализированное, как HttpClient Jetty. Самым первым шагом является добавление необходимой зависимости, которая предоставит нам доступ к базовому HttpClient.

# Gradle    
implementation group: 'org.eclipse.jetty', name: 'jetty-reactive-httpclient', version: '1.1.4'

# Maven

    org.eclipse.jetty
    jetty-reactive-httpclient
    1.1.4

Как только у нас появится доступ к нужным нам классам, необходимо создать два компонента.

Первый – это наш метод улучшения. Этот метод принимает запрос и возвращает его обратно, позволяя нам перехватывать и регистрировать все интересующие нас фрагменты.

Вот пример метода улучшения и его вывод:

// org.eclipse.jetty.client.api.Request
private Request enhance(Request inboundRequest) {
    StringBuilder log = new StringBuilder();
    // Request Logging
    inboundRequest.onRequestBegin(request ->
            log.append("Request: \n")
            .append("URI: ")
            .append(request.getURI())
            .append("\n")
            .append("Method: ")
            .append(request.getMethod()));
    inboundRequest.onRequestHeaders(request -> {
        log.append("\nHeaders:\n");
        for (HttpField header : request.getHeaders()) {
            log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
        }
    });
    inboundRequest.onRequestContent((request, content) ->
            log.append("Body: \n\t")
            .append(content.toString()));
    log.append("\n");

    // Response Logging
    inboundRequest.onResponseBegin(response ->
            log.append("Response:\n")
            .append("Status: ")
            .append(response.getStatus())
            .append("\n"));
    inboundRequest.onResponseHeaders(response -> {
       log.append("Headers:\n");
       for (HttpField header : response.getHeaders()) {
           log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
       }
    });
    inboundRequest.onResponseContent(((response, content) -> {
        var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
        log.append("Response Body:\n" + bufferAsString);
    }));

    // Add actual log invocation
    logger.info("HTTP ->\n");
    inboundRequest.onRequestSuccess(request -> logger.info(log.toString()));
    inboundRequest.onResponseSuccess(response -> logger.info(log.toString()));

    // Return original request
    return inboundRequest;
}

Объект запроса предоставляет множество крючков для доступа и захвата данных, которые вы хотите зарегистрировать. Документы по интерфейсу находятся здесь -> https://www.eclipse.org/jetty/javadoc/9.4.8.v20171121/org/eclipse/jetty/client/api/Request.html

Чтобы наш метод enhance выполнялся во время вызова нашего WebClient, мы собираемся создать ваш собственный HttpClient и использовать его вместо JettyClientHttpConnector по умолчанию. Вот пример компонента, который предоставляет веб-клиент:

@Bean
public WebClient jettyHttpClient() {
    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);
        }
    };
    return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient)).build();
}

Результат

Теперь, используя WebClient, который мы запустили с помощью нашего базового HttpClient, мы получаем следующий вывод:

2020-10-08 15:00:00.000  INFO 2100 --- [   @cafebabe-37] 
c.s.l.examples.JettyWebClient            : 
Request: 
URI: http://httpbin.org/get
Method: GET
Headers:
        Accept-Encoding : gzip
        User-Agent : Jetty/9.4.31.v20200723
        Accept : */*
        Host : httpbin.org
Response:
Status: 200
Headers:
        Date : Thu, 08 Oct 2020 20:24:17 GMT
        Content-Type : application/json
        Content-Length : 297
        Connection : keep-alive
        Server : gunicorn/19.9.0
        Access-Control-Allow-Origin : *
        Access-Control-Allow-Credentials : true
Response Body:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Jetty/9.4.31.v20200723", 
    "X-Amzn-Trace-Id": "Root=1-5f7f7571-    157328ac70a3bd900bc1c8bc"
  }, 
  "origin": "12.345.678.91", 
  "url": "http://httpbin.org/get"
}

Одним из преимуществ использования этого подхода по сравнению с подходом Netty является то, что для этого не требуется вносить изменения в журнал. Это также позволяет вам при необходимости сохранять данные из каждого запроса.

Моя цель в этом посте состояла в том, чтобы прояснить реализацию, которая дает нам наиболее детальный контроль над данными запроса и ответа.

Приведенный выше код доступен в полностью рабочем приложении, расположенном в следующем репозитории: https://github.com/StevenPG/logging-with-webclient

Оригинал: “https://dev.to/stevenpg/logging-with-spring-webclient-2j6o”