HTTP 메소드에 의한 평범한 기능 템플릿을 제공해주고, 더 나아가 특별한 케이스를 지원하는 exchange
와 execute
메소드를 제공해준다.
RestTemplate은 기본적으로 connection pool을 사용하지 않기 때문에 매 요청마다 handshake를 수행한다.
이를 방지하기 위해 다음과 같은 설정으로 Custom RestTemplate
을 빈으로 등록하여 사용할 수 있다.
@Configuration
public class HttpConnectionConfig {
@Bean
public RestTemplate getCustomRestTemplate() {
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(2000);
httpRequestFactory.setReadTimeout(3000);
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.build();
httpRequestFactory.setHttpClient(httpClient);
return new RestTemplate(httpRequestFactory);
}
}
httpClient를 사용하기 위해 아파치 의존을 추가해야 한다
// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
HttpComponentsClientHttpRequestFactory: HttpClient를 사전구성 할 수 있는 메서드 제공
requestBody
에 대해 버퍼링을 할지 하지 않을지. 공식 문서에 의하면, Default는 true이나 매우 큰 응답 바디가 들어오는 경우 false로 세팅하기를 권장한다.HttpClient에 connection pool 설정
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.build();
MaxConnTotal
이 connection pool
의 갯수이고,
MaxConnPerRoute
는 IP, Port 하나 당 연결 제한 갯수이다.
RestTemplate
을 사용하기 위한 준비는 끝났고 주요 메소드는 다음과 같다.
메소드 명으로도 알 수 있다시피 Restful
을 준수하는 템플릿이다.
아래 예제는 getForEntity()
API를 사용하는 예이다.
public class RestTemplateBasicLiveTest {
private RestTemplate restTemplate;
private static final String fooResourceUrl = "http://localhost:" + APPLICATION_PORT + "/foos";
@BeforeEach
public void init() {
restTemplate = new RestTemplate();
}
@Test
public void givenResourceUrl_whenSendGetForRequestEntity_thenStatusOk() throws Exception{
//given
//when
ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl, String.class);
//then
Assertions.assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
HTTP 응답에 대한 전체 엑세스 권한이 있으므로 상태 코드를 확인하여 작업이 성공했는지 확인하거나 응답의 실제본문으로 작업하는 것과 같은 작업을 수행할 수 있다.
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
JsonNode name = root.path("name");
Assertions.assertThat(name.asText()).isNotNull();
여기에서는 응답 본문을 표준 문자열로 사용하고 Jackson(및 Jackson이 제공하는 JSON 노드 구조)을 사용하여 일부 세부 정보를 확인한다.
응답을 Resource DTO에 직접 매핑할수도 있다.
public class Foo implements Serializable {
private Long id;
private String name;
// 기본생성자, Getter, Setter 필수
이제 템플릿에서 getForObjectAPi
를 간단히 사용할수 있다.
@Test
public void givenResourceUrl_whenRetrievingResource_thenCorrect() throws Exception{
//given
//when
Foo foo = restTemplate.getForObject(fooResourceUrl, Foo.class);
//then
assertThat(foo.getName()).isEqualTo("yyh");
assertThat(foo.getId()).isEqualTo(1L);
}
더 일반적인 방법으로 이동하기 전에 HEAD를 사용하는 방법을 간단히 살펴보자.
여기서에서는 headForHeaders() API
를 사용해보자
@Test
public void givenFooService_whenCallHeadForHeaders_thenReceiveAllHeadersForThatResource() throws Exception{
//given
//when
HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);
//then
assertThat(MediaType.APPLICATION_JSON).isIn(httpHeaders.getContentType());
}
새 리소스를 생성하기 위해 postForLocation()
,postForObject()
또는 postForEntity()
API를 사용할 수 있다.
첫번째는 새로 생성된 리소스의 URI를 반환하고 두번째는 리소스 자체를 반환한다.
@Test
public void givenFooService_whenPostForObject_thenCreatedObjectIsReturned() throws Exception{
//given
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
//when
Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);
//then
assertThat(foo).isNotNull();
assertThat(foo.getName()).isEqualTo("bar");
}
전체 리소스를 반환하는 대신 새로 생성된 리소스의 위치만 반환하는 작업을 살펴보자.
@Test
public void givenFooService_whenPostForLocation_thenCreatedLocationIsReturned() throws Exception{
//given
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
//when
URI location = restTemplate.postForLocation(fooResourceUrl, request);
//then
assertThat(location).isNotNull();
}
모든 HTTP 요청 메소드를 지원하며 원하는 서버에 요청시켜주는 메소드
@Test
public void givenFooService_whenExchange_thenItIsPosted() throws Exception{
//given
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
//when
ResponseEntity<Foo> response = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);
//then
Foo foo = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(foo.getName()).isEqualTo("bar");
}
Body는 보통 key, value의 쌍으로 이루어지기 때문에 자바에서 제공해주는 MultiValueMap 타입을 사용해야한다.
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add('')
MultiValueMap 타입으로 만들어준 변수에 add()를 사용해 보낼 데이터를 추가해준다.
HTTP POST를 요청할때 보내는 데이터(Body)를 설명해주는 헤더(Header)도 만들어서 같이 보내줘야 한다.
HttpHeaders headers = new HttpHeaders();
headers.add("");
Spring Framework에서 제공해주는 HttpHeaders 클래스는 Header를 만들어준다.
add()를 사용해 Header에 들어갈 내용을 추가해주자.
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
Spring Framework에서 제공해주는 HttpEntity 클래스는 Header와 Body를 합쳐준다.
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
"https://{요청할 서버 주소}", //{요청할 서버 주소}
HttpMethod.POST, //{요청할 방식}
entity, // {요청할 때 보낼 데이터}
String.class {요청시 반환되는 데이터 타입}
);
Spring Framework에서는 서버에 요청을 편하게 하기 위한 RestTemplate 클래스를 제공해준다.
다음으로 POST 메서드를 사용하여 데이터를 submit 하는 방법을 살펴보자
먼저 HTTP의 Content-Type 헤더를 application/x-www-form-urlencoded로 설정해야 한다.
이렇게 하면 &로 구분된 이름/값 쌍을 포함하는 큰 쿼리 문자열을 서버로 보낼 수 있다.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
양식 변수를 LinkedMultiValueMap
으로 매핑할수있다.
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("id", "1");
다음으로 HttpEntity
인스턴스를 사용하여 요청을 작성한다.
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
마지막으로 restTemplate.postForEntity()
를 호출하여 Rest 서비스에 연결한다.
ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl, request, String.class);
//then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
정리
@Test
public void givenFooService_whenFormSubmit_thenResourceIsCreated() throws Exception{
//given
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("id", "1");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
//when
ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl, request, String.class);
//then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
}
template.put API
가 매우 간단하기 때문에 이 예제에서 구체적으로 exchange()
API를 살펴보자.
API에 대한 간단한 PUT 작업으로 시작하고 작업이 클라이언트에 본문을 반환하지 않는다는 점을 염두에 두십시오.
@Test
public void givenFooService_whenPutExistingEntity_thenItIsUpdated() throws Exception{
final HttpHeaders headers = prepareBasicAuthHeaders();
final HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"), headers);
// Create Resource
final ResponseEntity<Foo> createResponse = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);
// Update Resource
final Foo updatedInstance = new Foo("newName");
updatedInstance.setId(createResponse.getBody()
.getId());
final String resourceUrl = fooResourceUrl;
final HttpEntity<Foo> requestUpdate = new HttpEntity<>(updatedInstance, headers);
restTemplate.exchange(resourceUrl, HttpMethod.PUT, requestUpdate, Void.class);
//then
}
다음으로 요청 콜백을 사용하여 PUT을 실행할 것이다.
필요한 모든 헤더와 요청 본문을 설정할 수 있는 콜백을 준비해야 한다.
private RequestCallback requestCallback(final Foo updatedInstance) {
return clientHttpRequest -> {
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(clientHttpRequest.getBody(), updatedInstance);
clientHttpRequest.getHeaders()
.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
clientHttpRequest.getHeaders()
.add(HttpHeaders.AUTHORIZATION, "Basic " + getBase64EncodedLogPass());
};
}
다음으로 POST 요청으로 리소스를 생성한다.
final HttpHeaders headers = prepareBasicAuthHeaders();
final HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"), headers);
// Create entity
ResponseEntity<Foo> response = restTemplate.exchange(fooResourceUrl, HttpMethod.POST, request, Foo.class);
그런 다음 리소스를 업데이트 한다.
// Update entity
final Foo updatedInstance = new Foo("newName");
updatedInstance.setId(response.getBody()
.getId());
final String resourceUrl = fooResourceUrl + '/' + response.getBody()
.getId();
restTemplate.execute(resourceUrl, HttpMethod.PUT, requestCallback(updatedInstance), clientHttpResponse -> null);
기존 리소스를 제거하기 위해 delete() API를 빠르게 사용해보자.
String entityUrl = fooResourceUrl + "/" + existingResource.getId();
restTemplate.delete(entityUrl);
ClientHttpRequestFactory
를 사용하여 RestTemplate
이 시간 초과되도록 구성해보자.
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int timeout = 5000;
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
= new HttpComponentsClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(timeout);
return clientHttpRequestFactory;
}
그리고 추가 구성 옵션에 HttpClient
를 사용할 수 있다.
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int timeout = 5000;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
Spring Framework 5
부터 WebFlux
스택과 함께 Spring
은 WebClient
라는 새로운 HTTP 클라이언트를 도입했습니다 .
WebClient
는 RestTemplate
에 대한 현대적인 대체 HTTP 클라이언트 입니다. 기존의 동기 API를 제공할 뿐만 아니라 효율적인 비차단 및 비동기 접근 방식도 지원합니다.
즉, 새 응용 프로그램을 개발하거나 이전 응용 프로그램을 마이그레이션하는 경우 WebClient
를 사용하는 것이 좋습니다 . 앞으로 RestTemplate
은 향후 버전에서 더 이상 사용되지 않습니다.
https://www.baeldung.com/rest-template
https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-resttemplate
https://withseungryu.tistory.com/116
https://a1010100z.tistory.com/125