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