1. Обзор
В этом руководстве мы узнаем, как реализовать эффективную регистрацию запросов/ответов RestTemplate .
Это особенно полезно для отладки обмена между двумя серверами.
К сожалению, Spring Boot не предоставляет простого способа проверить или зарегистрировать простое тело ответа JSON.
Мы собираемся изучить несколько методов регистрации либо заголовков HTTP, либо, что наиболее интересно, тела HTTP.
Примечание . Spring RestTemplate
будет устаревшим и будет заменен WebClient
. Вы можете найти аналогичную статью с использованием WebClient
здесь: Logging Spring WebClient Calls .
2. Базовое ведение журнала с помощью RestTemplate
Приступим к настройке логгера RestTemplate
в файле application.properties
:
logging.level.org.springframework.web.client.RestTemplate=DEBUG
В результате мы можем видеть только основную информацию , такую как URL-адрес запроса, метод, тело и статус ответа:
o.s.w.c.RestTemplate - HTTP POST http://localhost:8082/spring-rest/persons
o.s.w.c.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
o.s.w.c.RestTemplate - Writing [my request body] with org.springframework.http.converter.StringHttpMessageConverter
o.s.w.c.RestTemplate - Response 200 OK
Однако здесь не записывается тело ответа , что очень жаль, потому что это самая интересная часть.
Чтобы решить эту проблему, мы выберем Apache HttpClient или перехватчик Spring.
3. Регистрация заголовков/тела с помощью Apache HttpClient
Во- первых, мы должны заставить RestTemplate
использовать реализацию Apache HttpClient .
Нам понадобится зависимость Maven :
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
При создании экземпляра RestTemplate
мы должны сообщить ему, что используем Apache HttpClient :
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Затем настроим клиентский логгер в файле application.properties :
logging.level.org.apache.http=DEBUG
logging.level.httpclient.wire=DEBUG
Теперь мы можем видеть как заголовки запроса/ответа, так и тело:
o.a.http.headers - http-outgoing-0 >> POST /spring-rest/persons HTTP/1.1
o.a.http.headers - http-outgoing-0 >> Accept: text/plain, application/json, application/*+json, */*
// ... more request headers
o.a.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.9 (Java/1.8.0_171)
o.a.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
org.apache.http.wire - http-outgoing-0 >> "POST /spring-rest/persons HTTP/1.1[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept: text/plain, application/json, application/*+json, */*[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/plain;charset=ISO-8859-1[\r][\n]"
// ... more request headers
org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "my request body"
org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json[\r][\n]"
// ... more response headers
org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "21[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "["Lucie","Jackie","Danesh","Tao"][\r][\n]"
Однако эти журналы подробны и неудобны для отладки .
Мы увидим, как решить эту проблему в следующей главе.
4. Запись тела с помощью перехватчика RestTemplate
В качестве другого решения мы можем настроить перехватчики для RestTemplate
.
4.1. Реализация перехватчика регистрации
Во-первых, давайте создадим новый LoggingInterceptor
для настройки наших журналов . Этот перехватчик регистрирует тело запроса как простой массив байтов. Однако для ответа нам нужно прочитать весь поток тела:
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
static Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public ClientHttpResponse intercept(
HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
LOGGER.debug("Request body: {}", new String(reqBody, StandardCharsets.UTF_8));
ClientHttpResponse response = ex.execute(req, reqBody);
InputStreamReader isr = new InputStreamReader(
response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr).lines()
.collect(Collectors.joining("\n"));
LOGGER.debug("Response body: {}", body);
return response;
}
}
Остерегайтесь, этот перехватчик влияет на само содержание ответа, как мы узнаем в следующей главе.
4.2. Использование перехватчика с RestTemplate
Теперь мы должны решить проблему потоковой передачи: поскольку перехватчик потребляет поток ответа, наше клиентское приложение увидит пустое тело ответа .
Чтобы избежать этого, мы должны использовать BufferingClientHttpRequestFactory
: он буферизует содержимое потока в память. Таким образом, он может быть прочитан дважды: один раз нашим перехватчиком и второй раз нашим клиентским приложением:
ClientHttpRequestFactory factory =
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);
Однако использование этой фабрики влечет за собой недостаток производительности , который мы опишем в следующем подразделе.
Затем мы можем добавить наш перехватчик журналирования в экземпляр RestTemplate
— мы добавим его после существующих перехватчиков, если они есть:
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new LoggingInterceptor());
restTemplate.setInterceptors(interceptors);
В результате в логах присутствует только необходимая информация:
c.b.r.l.LoggingInterceptor - Request body: my request body
c.b.r.l.LoggingInterceptor - Response body: ["Lucie","Jackie","Danesh","Tao"]
4.3. Недостаток перехватчика RestTemplate
Как упоминалось ранее, использование BufferingClientHttpRequestFactory
имеет серьезный недостаток: оно сводит на нет преимущества потоковой передачи. Как следствие, загрузка всех данных тела в память может привести к проблемам с производительностью нашего приложения. Хуже того, это может привести к OutOfMemoryError
.
Чтобы предотвратить это, один из возможных вариантов — предположить, что эти подробные журналы будут отключены при увеличении объема данных, что обычно происходит в рабочей среде. Например, мы можем использовать буферизованный экземпляр RestTemplate
, только если `` в нашем регистраторе включен уровень DEBUG :
RestTemplate restTemplate = null;
if (LOGGER.isDebugEnabled()) {
ClientHttpRequestFactory factory
= new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
restTemplate = new RestTemplate(factory);
} else {
restTemplate = new RestTemplate();
}
Точно так же мы обеспечим, чтобы наш перехватчик считывал ответ только тогда, когда включено ведение журнала DEBUG
:
if (LOGGER.isDebugEnabled()) {
InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr)
.lines()
.collect(Collectors.joining("\n"));
LOGGER.debug("Response body: {}", body);
}
5. Вывод
Ведение журнала запросов/ответов RestTemplate
не является простым делом, поскольку Spring Boot не включает его в готовом виде.
К счастью, мы увидели, что можем использовать регистратор Apache HttpClient для получения подробной трассировки обмениваемых данных.
Или мы можем реализовать собственный перехватчик, чтобы получать более удобочитаемые журналы . Однако это может привести к снижению производительности при работе с большими объемами данных.
Как всегда, исходный код этой статьи доступен на GitHub в тестовой
папке. В примере используется RestTemplate
в реальном тесте для конечной точки REST, определенной в том же проекте.