제육's 휘발성 코딩
반응형
반응형

1. RestTemplate 이란?

RestTemplate은 스프링 3.0 버전부터 지원하는 라이브러리로, REST 방식의 API를 요청하고 json, xml, String 등 응답받을 수 있다.

Spring 5부턴 WebFlux와 함께 WebClient를 도입하여 동기식 방식 및 비동기 접근을 지원하고 있다.

 

 

1.1. RestTemplate 동작 과정

image

<code />
RestTemplate restTemplate = new RestTemplate();
  • 애플리케이션에서 Http Rest API 요청을 위한 RestTemplate을 생성한다.

 

<code />
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을 읽고 쓸 수 있게 해준다.

 

<code />
/** * 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 가 수행한다.

 

<code />
public void setErrorHandler(ResponseErrorHandler errorHandler) { Assert.notNull(errorHandler, "ResponseErrorHandler must not be null"); this.errorHandler = errorHandler; } public ResponseErrorHandler getErrorHandler() { return this.errorHandler; }
  • ClientHttpResponse 에 오류가 있으면 에러 핸들링 처리를 한다. 문제가 없으면 MessageConverter를 통해 결과를 애플리케이션에 돌려준다.

 

1.2. 요청 메서드

  • 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 콜백을 수정할 수 있다.

 

1.3. RestTemplate 사용 예제

<code />
@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 응답을 만들어주는 컨트롤러 메서드를 만들어주자.

 

<code />
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과 파라미터를 조합하여 사용할 수 있다.

 

1.4. RestTemplate JSON 요청 사용 예제

<code />
// 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을 발급해주는 코드를 만들어보자.

 

1.4.1. RestTemplate 응답부

<code />
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; } }
  • 응답 정보를 만들기 위한 오브젝트 생성

 

<code />
@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 요청을 받아 응답해주기 위한 컨트롤러 생성하자. 요청 메시지 바디에 대한 검증은 생략하였다.

 

image

  • 포스트맨으로 요청 시 정상 응답을 반환하는 것을 확인할 수 있다.

 

<code />
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 명령어를 통해서도 확인해볼 수 있다.

 

image

  • 정상적으로 요청이 된다면, 다음과 같은 결과를 받을 수 있다.

 

1.4.2. RestTemplate 요청부

<code />
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을 만들어주자.

 

<code />
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); } }
  • 테스트 시 통과하는 것을 확인할 수 있다.

 

1.5. 정리

  • RestTemplate은 요청 url로 String 또는 URI 형태를 받는다.
  • JSON 데이터 요청을 처리하기 위해 LinkedMultiValueMap 사용도 가능하다.
  • RestTemplateHttpClient를 추상화하고 있으며, Connection Pooling을 지원하지 않는다. 따라서 사용한 소켓은 TIME_WAIT 상태가 되며, 재사용하지 못하게 된다. apache에서 제공하는 HttpClient를 구현하여 커넥션 풀 설정을 하자.

1.6.  

1.7. REFERENCES


반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^