반응형
반응형
RestTemplate 이란?
RestTemplate
은 스프링 3.0 버전부터 지원하는 라이브러리로, REST 방식의 API를 요청하고 json
, xml
, String
등 응답받을 수 있다.
Spring 5부턴 WebFlux
와 함께 WebClient
를 도입하여 동기식 방식 및 비동기 접근을 지원하고 있다.
RestTemplate 동작 과정
RestTemplate restTemplate = new RestTemplate();
- 애플리케이션에서 Http Rest API 요청을 위한
RestTemplate
을 생성한다.
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
public RestTemplate() {
// 자동등록 리스트
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
//...
}
MessageConverter
를 이용해서 객체를 메시지 형태에 맞춰 request body에 변환하여 담아준고, body의 타입을 Content-type으로 명시해준다.- 메시지 컨버터를 통해 jackson 라이브러리에 포함된 ObjectMapper를 통해 JSON을 읽고 쓸 수 있게 해준다.
/**
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
* @param requestFactory the HTTP request factory to use
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
ClientHttpRequestFactory
에서 ClientHttpRequest를 받아와 요청을 전달한다. 실제로 요청을 수행하는 객체는ClientHttpRequest
가 수행한다.
public void setErrorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ResponseErrorHandler must not be null");
this.errorHandler = errorHandler;
}
public ResponseErrorHandler getErrorHandler() {
return this.errorHandler;
}
ClientHttpResponse
에 오류가 있으면 에러 핸들링 처리를 한다. 문제가 없으면 MessageConverter를 통해 결과를 애플리케이션에 돌려준다.
요청 메서드
getForObject(String url, ...), postForObject(String url, ...)
: GET, POST 방식으로 요청하며, 객체로 결과를 반환한다.getForEntity(String url, ...), postForEntity(String url, ...)
: GET, POST 방식으로 요청하며,ResponseEntity
로 결과를 반환한다.postForLocation(String url, ...)
: POST 방식으로 요청하며, 헤더에 저장된 URL을 결과로 반환한다.exchange(String url, HttpMethod method, ...)
: 모든 HTTP 메서드를 사용할 수 있으며, 그에 맞는 결과 값을 반환한다.execute(String url, HttpMethod method, ...)
: 모든 요청메서드들이 마지막에 실행하는 메서드로, Request나 Response 콜백을 수정할 수 있다.
RestTemplate 사용 예제
@GetMapping("/getForAny/{id}")
public ResponseEntity<User> getForAny(@PathVariable Long id) {
User user = User
.builder()
.id(id)
.name("sasca")
.build();
return new ResponseEntity<>(user, HttpStatus.OK);
}
- getForEntity와 getForobject 테스트를 위해,
GET
요청을 받았을 때 OK 응답을 만들어주는 컨트롤러 메서드를 만들어주자.
private RestTemplate restTemplate;
private static final String DEFAULT_URL = "http://localhost:8080";
private Map<String, Object> params;
@BeforeEach
public void before() {
restTemplate = new RestTemplate();
params = new HashMap<>();
params.put("id", 1);
}
@Test
@DisplayName("getForObject 테스트")
public void getForEntity() throws IOException {
UriComponents uriComponents = UriComponentsBuilder
.fromHttpUrl(DEFAULT_URL + "/getForAny")
.path("/{id}")
.buildAndExpand(params);
User user = restTemplate.getForObject(uriComponents.toUriString(), User.class);
Assertions.assertThat(user.getName()).isEqualTo("sasca");
}
@Test
@DisplayName("getForEntity 테스트")
public void getForObject() {
String url = DEFAULT_URL + "/getForAny/{id}";
ResponseEntity<User> user = restTemplate.getForEntity(url, User.class, params);
Assertions.assertThat(user.getBody().getName()).isEqualTo("sasca");
}
@Test
@DisplayName("exchange 테스트")
public void exchangeToGetForObject() {
String url = DEFAULT_URL + "/getForAny/{id}";
ResponseEntity<User> user = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(User.class), User.class, params);
Assertions.assertThat(user.getBody().getName()).isEqualTo("sasca");
}
getForEntity
,getForObject
모두 테스트를 통과한다.RestTemplate
은 uri를 String 타입과 URI 타입으로 받을 수 있다. 따라서,UriComponent
를 사용하여, url과 파라미터를 조합하여 사용할 수 있다.
RestTemplate JSON 요청 사용 예제
// POST 요청부
{
"info" : {
"country" : "korea",
"name" : "sasca",
"secretIdentity" : "unknown",
},
"reqData" : {
"id" : "sasca37",
"password" : "sasca37"
}
}
// POST 응답부
{
"code" : {
"result" : "200",
"message" : "success"
},
"resData" : {
"token" : "tokenURL!!!",
"expirationTime" : "20230101"
}
}
- 다음과 같이 JSON 포맷의 사용자 정보가 POST 요청으로 오면 응답으로 token을 발급해주는 코드를 만들어보자.
RestTemplate 응답부
package sasca.springjsonapi.entity.entity;
import lombok.*;
@AllArgsConstructor
@Builder
@Getter
@NoArgsConstructor
public class RespEntity {
private Code code;
private ResData resData;
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class Code {
private String result;
private String message;
}
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class ResData {
private String token;
private String expirationTime;
}
}
- 응답 정보를 만들기 위한 오브젝트 생성
@RestController
public class JsonRespController {
@PostMapping("/getToken")
public ResponseEntity<Object> requestTest(HttpServletResponse response) throws JsonProcessingException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
ObjectMapper objectMapper = new ObjectMapper();
RespEntity resp = RespEntity.builder()
.code(RespEntity.Code.builder().result("200").message("success").build())
.resData(RespEntity.ResData.builder().token(TokenUtil.TOKEN_INFO).expirationTime("20230130000000").build())
.build();
String messageBody = objectMapper.writeValueAsString(resp);
return ResponseEntity.status(HttpStatus.OK).body(messageBody);
}
}
- POST 요청을 받아 응답해주기 위한 컨트롤러 생성하자. 요청 메시지 바디에 대한 검증은 생략하였다.
- 포스트맨으로 요청 시 정상 응답을 반환하는 것을 확인할 수 있다.
curl --connect-timeout 15 \
-i \
-H 'Content-Type: application/json' \
-d '{ "info" : { "country" : "korea", "name" : "sasca", "secretIdentity" : "nknown"}, "reqData" : { "id" : "sasca37","password" : "sasca37"}}' \
-X POST http://localhost:8080/getToken
- 터미널에서 CURL 명령어를 통해서도 확인해볼 수 있다.
- 정상적으로 요청이 된다면, 다음과 같은 결과를 받을 수 있다.
RestTemplate 요청부
package sasca.springjsonapi.util;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import sasca.springjsonapi.entity.entity.*;
import java.io.IOException;
public class TokenUtil {
public static final String TARGET_URL = "http://localhost:8080/getToken";
public static final String TOKEN_INFO = "tokenURL!!!";
private final int connTimeout = 10 * 1000;
private final int connReqTimeout = 10 * 1000;
private final int readTimeout = 10 * 1000;
private int maxConnTotal = Runtime.getRuntime().availableProcessors();
private int maxConnPerRoute = Runtime.getRuntime().availableProcessors();
public String getToken(ReqEntity req) throws IOException {
// org.apache.http.client.HttpClient 생성 후 Connection Pool 설정
HttpClient client = HttpClientBuilder
.create()
.setMaxConnTotal(maxConnTotal) // 최대 오픈 커넥션 수 제한
.setMaxConnPerRoute(maxConnPerRoute) // 호스트(IP, PORT 조합)에 대한 커넥션 수 제한
.build();
// ClientHttpRequestFactory의 구현체인 HttpComponentsClientHttpRequestFactory 생성
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(client); // HttpClient를 직접 설정하지 않는 경우 생략해도 된다.
factory.setConnectTimeout(connTimeout); // 커넥션 타임아웃 설정
factory.setConnectionRequestTimeout(connReqTimeout); // 요청 커넥션 타임아웃 설정
factory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders header = new HttpHeaders();
header.setContentType(new MediaType("application", "json", StandardCharsets.UTF_8));
RespEntity token = restTemplate.postForObject(TARGET_URL, req, RespEntity.class);
return token.getResData().getToken();
}
}
- Token 발급을 위한 클래스를 만들었다.
RestTemplate
은 커넥션 풀링을 지원하지 않는다. 따라서 아파치에서 제공하는HttpClient
에 커넥션 설정을 지정하여,ClientHttpRequsetFactory
를 만든 후, RestTemplate을 만들어주자.
package sasca;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import sasca.springjsonapi.entity.entity.User;
import sasca.springjsonapi.util.TokenUtil;
import java.io.IOException;
public class RestTemplateTest {
@Test
public void postForObject() throws IOException {
Info info = Info
.builder()
.country("korea")
.name("sasca")
.secretIdentity("unknown")
.build();
ReqData reqData = ReqData
.builder()
.id("sasca37")
.password("sasca37")
.build();
ReqEntity req = new ReqEntity(info, reqData);
String token = new TokenUtil().getToken(req);
Assertions.assertThat(token).isEqualTo(TokenUtil.TOKEN_INFO);
}
}
- 테스트 시 통과하는 것을 확인할 수 있다.
정리
RestTemplate
은 요청 url로 String 또는 URI 형태를 받는다.- JSON 데이터 요청을 처리하기 위해
LinkedMultiValueMap
사용도 가능하다. RestTemplate
은HttpClient
를 추상화하고 있으며, Connection Pooling을 지원하지 않는다. 따라서 사용한 소켓은 TIME_WAIT 상태가 되며, 재사용하지 못하게 된다. apache에서 제공하는HttpClient
를 구현하여 커넥션 풀 설정을 하자.
REFERENCES
반응형