Hyeonuk_.log

[Spring] RestTemplate 업무 사용할 때 알아야할 점, 주의할 점 본문

Dev_.log/Spring

[Spring] RestTemplate 업무 사용할 때 알아야할 점, 주의할 점

Hyeonuk_. 2022. 2. 19. 06:00

 

업무에서 Spring RestTemplate을 사용하면서 알아낸 점을 공유드리려고 합니다.

일단, Spring 버전은 3.1 이었습니다. 재직하고 있는 회사 업무에서 제휴사 연계를 위해 RestTemplate을 사용하게 되었습니다.

 

1. 들어가며...


보통 Http 통신을 할 때 사용하는 RestTemplate, HttpUrlConnection, HttpClient, WebClient 에 대해 알아보도록 하겠습니다.

- RestTemplate
spring 3.0 부터 지원을 합니다. 스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿이며, HTTP 서버와의 통신을 단순화하고 RESTful 원칙을 지키며, 기계적이고 반복적인 코드를 줄여줍니다. JSON, XML 형식도 쉽게 변환해서 송수신을 할 수 있습니다. Blocking I/O 기반 Sync API(동기 방식)

- HttpUrlConnection
jdk 1.2 부터 내장되어 있으며, java.net 패키지에 있다. URL의 내용을 읽어오거나, URL 주소에 GET, POST로 데이터를 전달 할 때 사용한다. 또한 http 프로토콜 이외에도 가능하다.(file 등) 보통 아래와 같이 사용한다.

제가 업무에서 사용하며 느낀 불편한 점은 응답코드가 4xx 거나 5xx 면 IOException 이 터진다는 점, 헤더에 인증을 위한 Authorization 값 셋팅의 어려움 등 상대기관과 연계함에 있어 발생할 수 있는 다양한 상황에 대해 유연하게 대처하기가 어려웠습니다. 이 점이 RestTemplate을 사용하게 된 이유이기도 합니다.

-HttpClient
3.x일 땐 apache commons 의 프로젝트였다가 승급해서, 아파치 탑 프로젝트가 되었습니다. 4.x부터는 Apache HttpComponents 로 불립니다. maven dependency 를 설정하거나, http://hc.apache.org/downloads.cgi 에서 다운로드할 수 있습니다. org.apache.http 패키지에 있습니다. 아직 제가 맡고 있는 업무에서 사용해보지는 않았지만 많은 곳에서 일반적으로 사용하고 있는 방식입니다.

- WebClient
Spring 5부터 나왔고 RestTemplate보다 WebClient를 권고한다고 한다. 아직은 한번도 사용해보지 않아서 다음에 기회가 되면 포스팅해보도록 하겠습니다.

 

2. RestTemplate은 어떻게 동작할까?

RestTemplate 동작원리
 
org.springframework.http.client 패키지에 있다. HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient 를 추상화(HttpEntity의 json, xml 등)해서 제공해줍니다. 따라서 내부 통신(HTTP 커넥션)에 있어서는 Apache HttpComponents 를 사용합니다.

(1) 어플리케이션이 RestTemplate를 생성하고, URI, HTTP메소드 등의 헤더를 담아 요청한다.
(2) RestTemplate 는 HttpMessageConverter 를 사용하여 requestEntity 를 요청메세지로 변환한다.
(3) RestTemplate 는 ClientHttpRequestFactory 로 부터 ClientHttpRequest 를 가져와서 요청을 보낸다.
(4) ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신한다.
(5) RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태운다.
(6) ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리한다.
(7) RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환한다.
(8) 어플리케이션에 반환된다.

처음 사용할 때는 위 프로세스를 눈여겨 보지 않고 사용했는데 위 프로세스를 꼭 알고 사용을 해야합니다.!!

 

3. 실제 사용

- 아래의 예제는 일반적인 사용법입니다. RestTemplate을 Bean으로 등록을 한 후, 아래와 같이 POST를 사용할 수 있습니다.

@Test
public void simpleRestTemplate() {
   Employee newEmployee = Employee.builder()
         .name("Frank")
         .address("TestAddress")
         .build();
 
   HttpHeaders headers = new HttpHeaders();
   headers.set("headerValue", "headerValue");
 
   HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);
   log.info("employee: {}", employee);
}
 

그러나 Pool 관리, Timeout 설정 등 커스터마이징을 하게되면 신경써야할 부분이 많아집니다.

- 아래는 사용한 커스터마이징하여 사용한 방법입니다.

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(5000); // 읽기시간초과, ms 
factory.setConnectTimeout(3000); // 연결시간초과, ms 
HttpClient httpClient = HttpClientBuilder.create() 
                        .setMaxConnTotal(100) // connection pool 적용 
                        .setMaxConnPerRoute(5) // connection pool 적용 
                        .build();
factory.setHttpClient(httpClient); // 동기실행에 사용될 HttpClient 세팅 
RestTemplate restTemplate = new RestTemplate(factory);
 

PoolingHttpClientConnectionManager를 사용하면서 pool 갯수, 하나의 경로에 할당할 갯수를 설정할 수 있습니다. 또한, 아래에는 없지만 Keep-Alive 설정, Retry 설정을 할 수 있습니다.

@Configuration
public class RestTemplateConfiguration {
 
    private HttpHostsConfiguration httpHostConfiguration;
    
    @Autowired
    public void setHttpHostsConfiguration(HttpHostsConfiguration httpHostConfiguration) {
        this.httpHostConfiguration = httpHostConfiguration;
    }
    
    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager result = new PoolingHttpClientConnectionManager();
        result.setMaxTotal(this.httpHostConfiguration.getMaxTotal());
        // Default max per route is used in case it's not set for a specific route
        result.setDefaultMaxPerRoute(this.httpHostConfiguration.getDefaultMaxPerRoute());
        // and / or
        if (!CollectionUtils.isEmpty(this.httpHostConfiguration.getMaxPerRoutes())) {
            for (HttpHostConfiguration httpHostConfig : this.httpHostConfiguration.getMaxPerRoutes()) {
                HttpHost host = new HttpHost(httpHostConfig.getHost(), httpHostConfig.getPort(),
                        httpHostConfig.getScheme());
                // Max per route for a specific host route
                result.setMaxPerRoute(new HttpRoute(host), httpHostConfig.getMaxPerRoute());
            }
        }
        return result;
    }
 
    @Bean
    public RequestConfig requestConfig() {
        return RequestConfig.custom()
                .setConnectionRequestTimeout(httpHostConfiguration.getConnectionRequestTimeout())
                .setConnectTimeout(httpHostConfiguration.getConnectionTimeout())
                .setSocketTimeout(httpHostConfiguration.getSocketTimeout())
                .build();
    }
 
    @Bean
    public CloseableHttpClient httpClient() {
        return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager())
                .setDefaultRequestConfig(requestConfig()).build();
    }
 
    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient());
        return new RestTemplate(requestFactory);
    }
}
 

 

부족한 글 읽어주셔서 감사합니다.

 

Comments