Весна становится реактивной. Вам достаточно одного взгляда на доклады, представленные на конференции 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' # Mavenorg.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”