티스토리 뷰

Server

[Spring] RestTemplate 알아보기

니용 2020. 7. 14. 12:19
반응형

 

http / https 서버와의 통신을 위해 이전에는 HttpURLConnection과 같은 Object를 많이 사용했었습니다. 

이번에는 RestTemplate을 사용하여 서버간 통신을 해보려고 합니다.

 

1. 개요

Spring 3.0부터 지원하는 템플릿이며 RESTful원칙을 지킵니다. 코드도 간소화됩니다.

동기방식을 지원합니다.

AsyncRestTemplate도 있습니다. Spring 4.0부터 지원하며, 비동기 방식을 지원하지만 5.0에서 deprecated 되었습니다.

WebClient도 있습니다. Spring 5.0부터 지원하며, 논블럭-비동기 방식을 지원합니다.

 

2. 이전에 사용했던 방식

import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

public class HttpConnectionUtil {
    private void sendApiUsingHttpConnection(Map param) {
        try {
            String domain = "http://domain.kr";
            String url = domain + "/send/to/anywhere/";
            StringBuilder sb = new StringBuilder();
            sb.append("&userId=").append(param.get("userId"))
                .append("&column1=").append(param.get("column1"))
                .append("&column2=").append(param.get("column2"))
                .append("&column3=").append(param.get("column3"))
                .append("&sendto=").append(param.get("sendto"));

            HttpURLConnection conn = (HttpURLConnection) new URL(url + URLEncoder.encode(sb.toString(), "UTF-8")).openConnection();
            log.debug("Connection : " + conn);
            log.debug("ResponseCode : " + conn.getResponseCode());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

문제점으로는 응답 코드에 따라 IOException이 호출됩니다.

또, 동기식이므로 타임아웃을 설정할 수 없습니다.

 

 

3. RestTemplate

사용하기 전에 Factory를 통한 생성이 가능합니다. 이것이 가능한 이유는 Spring Framework 내에 내장되어 있는 라이브러리이기 때문에 추상화가 가능하기 때문입니다. 즉, 기본적으로 제공하는 기본값들을 개발자가 설정할 수 있는 일종의 @Component가 가능하기 때문입니다. 

 

RestTemplate가 동작되는 원리

@Component
public class RestTemplateFactory {

    public RestTemplate getCustomizingRT() {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); 
        factory.setReadTimeout(5000); // 읽기시간초과 기준 시간를 의미 단위는 ms 
        factory.setConnectTimeout(3000); // 연결시간초과 기준 시간를 의미 단위는 ms 
        factory.setHttpClient(
        // 동기실행에 사용될 HttpClient 세팅 
            HttpClientBuilder.create() 
                .setMaxConnTotal(100) // 최대 Connection Pool Size를 의미
                .setMaxConnPerRoute(5) // 라우터당 최대 Connection Size
                .build()); 

        RestTemplate restTemplate = new RestTemplate(factory);
        return restTemplate;
    }
}

 

사용법

기본적으로 제공되는 메소드는 다음과 같습니다.

RestTemplate Method HTTP Method Description
execute Any Request/Response콜백을 수정합니다 restTemplate.execute();
exchange Any 헤더를 세팅해서 HTTP Method를 호출하고 원하는 클래스타입의 ResponseEntity 값으로 반환받습니다 ResponseEntity<ClassName> result = restTemplate.exchange(uri.toString(), HttpMethod.GET, entity, ClassName.class);
getForObject GET 주어진 URL 주소로 GET 메소드의 객체로 결과를 반환받습니다
getForEntity GET 주어진 URL 주소로 GET 메소드로 ResponseEntity로 결과를 반환받습니다
postForLocation POST POST 요청을 보내고 결과로 헤더에 저장된 URI를 결과로 반환받습니다
postForObject POST POST 요청을 보내고 객체로 결과를 반환받습니다
postForEntity POST POST 요청을 보내고 결과로 ResponseEntity로 반환받습니다
put PUT 주어진 URL 주소로 HTTP PUT 메소드를 실행합니다
delete DELETE 주어진 URL 주소로 HTTP DELETE 메소드를 실행합니다
headForHeaders HEAD 헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메소드를 사용합니다
optionsForAllow OPTIONS 주어진 URL 주소에서 지원하는 HTTP 메소드를 조회합니다

 

아래 코드는 JUnit 테스트 코드입니다.

@RunWith(SpringRunner.class)
@SpringBootTest
public class RestTemplateJUnitTestClass {

    @Autowired RestTemplateFactory restTemplate;
    @Test
    public void testGetForObject() {
       Employee employee = restTemplate.getForObject(BASE_URL + "/{id}", Employee.class, 25);
       log.info("employee: {}", employee);
    }
    
    @Test
    public void testGetForEntity() {
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(BASE_URL + "/{id}", String.class, 25);
        log.info("statusCode: {}", responseEntity.getStatusCode());
        log.info("getBody: {}", responseEntity.getBody());
    }

    @Test
    public void testGetForEntitySendWithPathVariables() {
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("name", "Frank Oh");
        params.add("country", "US");
        ResponseEntity<Employee> responseEntity = restTemplate.getForEntity(BASE_URL + "/{name}/{country}", Employee.class, params);
        log.info("statusCode: {}", responseEntity.getStatusCode());
        log.info("getBody: {}", responseEntity.getBody());
    }
    
    @Test
    public void testPostForObjectSendExcludingHeader() {
        Employee newEmployee = Employee.builder()
                .name("Frank")
                .address(Address.builder()
                    .country("US")
                    .build())
                .build();
        
        Employee employee = restTemplate.postForObject(BASE_URL + "/employee", newEmployee, Employee.class);
        log.info("employee: {}", employee);
    }
    
    @Test
    public void testPostForObjectSendWithHeader() {
        Employee newEmployee = Employee.builder()
                .name("Frank")
                .address(Address.builder()
                    .country("US")
                    .build())
                .build();
        
        HttpHeaders headers = new HttpHeaders();
        headers.set("headerTest", "headerValue");
            
        HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
            
        Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);
        log.info("employee: {}", employee);
    }
    
    @Test
    public void testPostForEntityReturnByString() {
        Employee newEmployee = Employee.builder()
                .name("Frank")
                .address(Address.builder()
                    .country("US")
                    .build())
                .build();
            
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(BASE_URL + "/employee", newEmployee, String.class);
        log.info("statusCode: {}", responseEntity.getStatusCode());
        log.info("getBody: {}", responseEntity.getBody());
    }
    
    @Test
    public void testPostForLocation() {
        Employee newEmployee = Employee.builder()
                .name("Frank")
                .address(Address.builder()
                    .country("US")
                    .build())
                .build();
            
        HttpEntity<Employee> request = new HttpEntity<>(newEmployee);
            
        URI location = restTemplate.postForLocation(BASE_URL + "/employee/location", request);
        log.info("location: {}", location);
    }
    
    @Test
    public void testDelete() {
        Map<String, String> params = new HashMap<>();
        params.put("name", "Frank");
        restTemplate.delete(BASE_URL + "/employee/{name}", params);
    }
    
    @Test
    public void testPut() {
        Map<String, String> params = new HashMap<>();
        params.put("name", "Frank");
        Address address = Address.builder()
                .city("Columbus")
                .country("US")
                .build();
        restTemplate.put(BASE_URL + "/employee/{name}", address, params);
    }
    
    @Test
    public void testExchange() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> request = new HttpEntity<>("Hello World!", headers);
        log.info("request: {}", request);	 
        ResponseEntity<Employee> empEntity = restTemplate.exchange(BASE_URL + "/exchange/employee/{id}", HttpMethod.GET, request, Employee.class, 50);
        log.info("empEntity: {}", empEntity);
    }
    
    @Test
    public void testOptionsForAllow() {
        final Set<HttpMethod> optionsForAllow = restTemplate.optionsForAllow(BASE_URL + "/employee");	 
        log.info("optionsForAllow: {}", optionsForAllow);
    }
    
    @Test
    public void testExecute() {
        Address address = Address.builder()
                .city("Columbus")
                .country("US")
                .build();
        restTemplate.execute(BASE_URL + "/employee/{name}", HttpMethod.PUT, requestCallback(address), clientHttpResponse -> null, "frank");
    }
}

 

요청 URL 방식

1. UriComponentBuilder로 파라미터를 붙일 수 있습니다.

public URI getURIstandardFormat() {
	UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); 
	UriComponents uriComp = builder.scheme("http") 
		.host("localhost") 
		.port(8080) 
		.path("/users/{userId}/stores/{storeId}") 
		.build(); 
	uriComp = uriComp.expand("333", "1").encode();

	Uri uri = uriComp.toUri();
	return uri;
}

public URI getURIbyChaining() {
	URI uri = UriComponentsBuilder.newInstance() 
		.scheme("http") 
		.host("localhost") 
		.path("/users/{userId}/stores/{storeId}") 
		.build() 
		.toUri();
	return uri;
}

2. String.format으로도 가능합니다.

String response = template.getForObject("http://localhost:8080/stores/{storesId}", String.class, "1");

3. REST 형식으로도 전달이 가능합니다.

String response = template.getForObject("http://../users/{userId}/stores/{storesId}", String.class, "333", "1");

4. 보내는 파라미터를 Map으로 하여 전송도 가능합니다.

Map<String, Object> params = new HashMap<>(); 
params.put("userId", "333"); 
params.put("storesId", "1"); 
String response = restTemplate.getForObject("http://../users/{userId}/stores/{storesId}", String.class, params);

 

에러 응답 처리

1. HttpStatusCodeException : 응답 코드가 에러인 경우 발생합니다.

2. ResourceAccessException : 네트워크 연결에 문제가 있는 경우 발생합니다.

3. UnknownHttpStatusCodeException : 알 수 없는 응답코드인 경우 발생합니다.

 

그 외 AsyncRestTemplate를 이용한 비동기 처리

RestTemplate와 같은 메소드를 지원합니다.

하지만 결과를 바로 받지 않고 ListenableFutureCallback으로 받아 처리하게 됩니다. 

AsyncRestTemplate template = new AsyncRestTemplate(); 
template.getForEntity("http://www.naver.com", String.class) 
    .addCallback(new ListenableFutureCallback<ResponseEntity<String>>() { 
        public void onSuccess(ResponseEntity<String> result) { //... } 
        public void onFailure(Throwable ex) { //... } 
    });
반응형
댓글
공지사항