티스토리 뷰

반응형

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;

	}

}


즐코딩 되시길 바랍니다~

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
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
글 보관함