티스토리 뷰
Spring 에서 제공하는 RestTemplate 을 이용하면 다른 URL 을 호출하고 결과를 받아오는 코드를 쉽게 작성할 수 있습니다. 기존에는 Apache Commons 의 http 을 많이 이용했으나, Spring 에서 제공하는 RestTemplate 의 사용빈도가 점점 늘어나고 있습니다.
그런데, RestTemplate 을 기본 설정대로 사용하는 경우도 있지만, 추가적인 설정이 필요한 경우가 있습니다. 크게 두 가지라고 생각되는데, 하나는 HttpClient 설정을 별도로 하기 위한 것이고, 다른 하나는 로깅 때문입니다.
어떻게 이 두 가지를 적용하는지 알아보겠습니다.
먼저 설정을 하기 위해 @Configuration 애노테이션을 이용해 Class 을 하나 만듭니다. 그리고 그 안에 RestTemplate 을 위한 @Bean 을 생성합니다.
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); return restTemplate; } }
이 설정을 통해 RestTemplate 을 @Autowired 로 주입받아 사용할 수도 있게 됩니다.
1. HttpClient 설정
RestTemplate 을 사용할 때, 요청한 서버에서 응답을 늦게 주거나 할 경우 내 서버의 Thread 도 급격히 증가하거나 하는 문제가 발생할 수 있습니다. 이는 서버의 전체적인 장애로 연결되는데, 이러한 문제를 차단하기 위해 Connection 수를 제한하는 것들이 가능합니다.
이를 위해서 Spring 에서는 HttpComponentsClientHttpRequestFactory 을 제공합니다. 이 factory 을 이용해 timeout 등의 설정을 할 수 있습니다. 하지만, Connection 수의 제한은 할 수 없는데, 대신에 org.apache.http.client.HttpClient 을 매개변수로 받을 수 있는 method 을 제공합니다. 이 HttpClient 역시 Builder 을 이용해 새로운 객체를 만들어 추가적인 설정을 할 수 있고, MaxConn 등을 설정할 수 있습니다.
제가 주로 사용하는 설정은 아래와 같습니다.
@Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); HttpClient client = HttpClientBuilder.create() .setMaxConnTotal(50) .setMaxConnPerRoute(20) .build(); factory.setHttpClient(client); factory.setConnectTimeout(2000); factory.setReadTimeout(5000); return new RestTemplate(factory); }
HttpClient 에서 많은 설정을 변경할 수 있기 때문에 여러 상황에 따른 설정이 필요할 경우 위와 같이 처리하시면 됩니다.
2. 로깅
RestTemplate 을 이용해 요청을 할 경우 org.springframework.web.client 패키지를 debug 상태로 변경해도 로그를 확인할 수 있기 때문에 편리합니다. 하지만, 자세히 보시면 반쪽짜리 로그라는 것을 알 수 있습니다.
바로, request 에 대해서는 모든 정보가 보이는데 반해 response 에 대해서는 전송받은 내용을 보여주지 않는다는 문제입니다.
이 문제를 가장 쉽게 해결하는 방법은 위에서 언급한 HttpClient 가 속한 org.apache.http 패캐지의 로그를 대신 사용하는 것입니다. 그러면 더욱 더 자세한 형태의 로그를 볼 수 있습니다.
하지만, 이 경우에도 문제가 되는 것이 있습니다. 로그 내용의 특수문자나 한글 등을 유니코드 문자열로 치환해서 보여줍니다. 그래서 가독성이 현저히 떨어지게 됩니다.
그래서 별도로 로그를 남기도록 설정하는 방법을 사용해야 하는데, 이를 위해 RestTemplate 은 interceptor 을 추가할 수 있는 method 을 제공합니다. 바로 RestTemplate#setInterceptors 입니다. 이를 위해 Spring 이 제공하는 ClientHttpRequestInterceptor 인터페이스를 구현하는 class 을 만들어야 합니다.
class RequestResponseLoggingInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 전 ClientHttpResponse response = execution.execute(request, body); // 후 return response; } }
Spring 에서 Interceptor 을 구현해보신 분들에게는 익숙한 코드일 겁니다. interceptor method 을 구현하는데, execute() method 을 실행하기 "전" 과 "후" 로 나뉘어서 어떠한 동작을 구현할 수 있습니다. 그래서 보통 request 에 대한 로그는 "전" 부분에, response 에 대한 부분은 "후" 부분에 기술합니다.
request 에서는 주로 getURI(), getMethod(), getHeaders() 와 함께 interceptor 의 매개변수인 body 을 String 으로 변환하여 출력하는 형태로 구현을 합니다. response 에서는 getStatusCode(), getStatusText(), getHeaders() 와 함께 getBody() 로 InputStream 을 획득하여 StreamUtils.copyToString() 으로 response 본문을 출력하는 구현을 주로 사용합니다.
주의할 것은 "후" 부분에서 Stream 을 받아서 응답 본문을 가져와야 하는데, execute() 실행 시 이미 Stream 이 닫혀버린 후라서 Exception 이 발생한다는 것입니다. 그래서 실제로 interceptor 을 등록하기 전에 RestTemplate 선언 시 이에 따른 조치가 필요합니다. 바로 위에서 만든 factory 을 BufferingClientHttpRequestFactory 의 생성자 매개변수로 전달하여 Stream 을 복제해서 사용해야 한다는 것입니다.
아래 예제대로 처리하면 로깅 출력에 문제가 없게 됩니다.
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); HttpClient client = HttpClientBuilder.create() .setMaxConnTotal(50) .setMaxConnPerRoute(20) .build(); factory.setHttpClient(client); factory.setConnectTimeout(2000); factory.setReadTimeout(5000); // 이 부분을 아래와 같이 수정합니다. // return new RestTemplate(factory); RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(factory)); restTemplate.setInterceptors(Collections.singletonList(new RequestResponseLoggingInterceptor())); return restTemplate; } } class RequestResponseLoggingInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 전 ClientHttpResponse response = execution.execute(request, body); // 후 return response; } }
즐코딩 되시길 바랍니다~
'Programming > Spring Boot 시작하기' 카테고리의 다른 글
Spring Boot 실행과 종료 시 특정 동작을 실행하도록 해보기 (2) | 2019.01.25 |
---|---|
외부 resource 을 서비스하기 (3) | 2019.01.24 |
Jackson MessageConverter 을 상속받아 수정하기 (0) | 2019.01.22 |
Spring Boot Java Config 으로 Jackson 설정하기 (0) | 2019.01.22 |
TaskExecutor 로 @Async 의 Thread 을 Pool 로 관리하기 (0) | 2019.01.22 |
- Total
- Today
- Yesterday
- paging
- Phabricator
- Redmine
- 내장 WAS
- 엘지
- 클라우드플레어
- SI
- Nas
- NoSQL
- docker
- messages.properties
- boot
- 도입기
- Spring Boot
- 페이징
- KDE
- OracleJDK
- 외장 WAS
- proxmox
- RestTemplate
- Spring MVC
- 워드프레스
- manjaro
- jooq
- java config
- git
- 프로젝트 규모
- couchbase
- Spring
- 시니어 프로그래머
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |