Rest Client, RestTemplate
by mignon25Rest Client
Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리
클라이언트와 서버의 관계는 상대적이다.
만약, A라는 서버가 HTTP 통신을 통해서 B라는 다른 서버의 리소스를 이용한다면 그 때만큼은 서버 A가 클라이언트가 된다.
애플리케이션 내부에서 다른 서버의 애플리케이션에 HTTP요청을 보내야 하는 경우에 Rest Client 라이브러리를 사용하여 요청할 수 있다.
RestTemplate
Spring에서 제공하는 Rest Client API
RestTemplate 객체 생성
- RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다.
- HttpComponentsClientHttpRequestFactory를 통해 Apache HttpCompoents 전달
(Apache HttpComponents를 사용하기 위해 dependencies 에 아래의 라이브러리 추가)
implementation 'org.apache.httpcomponents:httpclient'
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class RestTemplateExample {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
}
URI 생성
- RestTemplate 객체를 생성했다면 HTTP Request 를 전송할 Rest 엔드포인트의 URI 를 지정해주어야 한다.
- (참고) World Time API : http://worldtimeapi.org/
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
public class RestTemplateExample {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// (2) URI 생성
UriComponents uriComponents =
UriComponentsBuilder
.newInstance()
.scheme("http")
.host("worldtimeapi.org")
// .port(80)
.path("/api/timezone/{continents}/{city}")
.encode()
.build();
URI uri = uriComponents.expand("Asia", "Seoul").toUri();
}
}
- newInstance() : UriComponentsBuilder 객체 생성
- scheme() : URI의 scheme 설정
- host() : 호스트 정보 입력
- port() : 디폴트 값은 80이므로 80 포트를 사용하는 호스트라면 생략 가능
- path()
- URI 의 경로 (path) 입력
- 위의 예시에서는 {continents}, {city} 의 두 개의 템플릿 변수를 사용하고 있다.
- 두 개의 템플릿 변수는 uriComponents.expand("Asia", "Seoul").toUri(); 에서 expand() 메서드 파라미터의 문자열로 지정
- 즉, 빌드 타임에 {continents}는 "Asia", {city}는 "Seoul"로 변환된다.
- encode()
- URI에 사용된 템플릿 변수들을 인코딩해준다.
- 여기서의 인코딩 : non-ASCII 문자와 URI에 적절하지 않은 문자를 Percent Encoding
- Percent-Encoding
- expand() : 파라미터로 입력한 값을 URI 템플릿 변수의 값으로 대체
- toUri() : URI 객체 생성
요청 전송
1. getForObject() 를 이용한 문자열 응답 데이터 전달 받기
public class RestTemplateExample {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// (2) URI 생성
UriComponents uriComponents =
UriComponentsBuilder
.newInstance()
.scheme("http")
.host("worldtimeapi.org")
// .port(80)
.path("/api/timezone/{continents}/{city}")
.encode()
.build();
URI uri = uriComponents.expand("Asia", "Seoul").toUri();
// (3) Request 전송
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
}
// 출력 결과
abbreviation: KST
client_ip: 121.152.253.169
datetime: 2023-04-11T18:08:58.727097+09:00
day_of_week: 2
day_of_year: 101
dst: false
dst_from:
dst_offset: 0
dst_until:
raw_offset: 32400
timezone: Asia/Seoul
unixtime: 1681204138
utc_datetime: 2023-04-11T09:08:58.727097+00:00
utc_offset: +09:00
week_number: 15
getForObject(URI uri, Class<T> responsType)
- HTTP Get 요청을 통해 서버의 리소스 조회
- URI uri
- Request를 전송할 엔드포인트의 URI 객체 지정
- Class<T> responseType
- 응답으로 전달 받을 클래스의 타입 지정
- 위의 예시에서는 응답 데이터를 문자열로 받을 수 있도록 String.class 로 지정함
2. getForObjcet() 를 이용한 커스텀 클래스 타입으로 원하는 정보만 응답으로 전달받기
- 원하는 데이터만 전달받고 싶을 때 사용
- 클래스를 별도로 하나 생성
public class RestTemplateExample2 {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// (2) URI 생성
UriComponents uriComponents =
UriComponentsBuilder
.newInstance()
.scheme("http")
.host("worldtimeapi.org")
// .port(80)
.path("/api/timezone/{continents}/{city}")
.encode()
.build();
URI uri = uriComponents.expand("Asia", "Seoul").toUri();
// (3) Request 전송. WorldTime 클래스로 응답데이터를 전달받는다.
WorldTime worldTime = restTemplate.getForObject(uri, WorldTime.class);
System.out.println("datetime = " + worldTime.getDatetime());
System.out.println("timezone = " + worldTime.getTimezone());
System.out.println("day_of_week = " + worldTime.getDay_of_week());
}
}
// 출력 결과
datetime = 2023-04-11T18:24:26.844213+09:00
timezone = Asia/Seoul
day_of_week = 2
// 응답 데이터를 받기 위한 WorldTime 클래스
public class WorldTime {
private String datetime;
private String timezone;
private int day_of_week;
public String getDatetime() {
return datetime;
}
public String getTimezone() {
return timezone;
}
public int getDay_of_week() {
return day_of_week;
}
}
- WorldTime 클래스의 필드명인 datetime, timezone, day_of_week 정보만 전달 받고 있다.
- 조건
- 응답 데이터의 JSON 프로퍼티 이름과 클래스의 멤버변수 이름이 동일해야 한다.
- 해당 멤버 변수에 접근하기 위한 getter 메서드 역시 동일한 이름이어야 한다.
3. getForEntity() 를 사용하여 Response Body + Header 정보 전달받기
public class RestTemplateExample3 {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// (2) URI 생성
UriComponents uriComponents =
UriComponentsBuilder
.newInstance()
.scheme("http")
.host("worldtimeapi.org")
// .port(80)
.path("/api/timezone/{continents}/{city}")
.encode()
.build();
URI uri = uriComponents.expand("Asia", "Seoul").toUri();
// (3) Request 전송. ResponseEntity 로 헤더와 바디 정보를 모두 전달받을 수 있다.
ResponseEntity<WorldTime> response =
restTemplate.getForEntity(uri, WorldTime.class);
System.out.println("datetime = " + response.getBody().getDatetime());
System.out.println("timezone = " + response.getBody().getTimezone());
System.out.println("day_of_week = " + response.getBody().getDay_of_week());
System.out.println("HTTP Status Code = " + response.getStatusCode());
System.out.println("HTTP Status Value = " + response.getStatusCodeValue());
System.out.println("Content Type = " + response.getHeaders().getContentType());
System.out.println(response.getHeaders().entrySet());
}
}
// 출력 결과
datetime = 2023-04-11T18:35:56.057621+09:00
timezone = Asia/Seoul
day_of_week = 2
HTTP Status Code = 200 OK
HTTP Status Value = 200
Content Type = application/json;charset=utf-8
[access-control-allow-credentials=[true], access-control-allow-origin=[*], access-control-expose-headers=[], cache-control=[max-age=0, private, must-revalidate], content-type=[application/json; charset=utf-8], cross-origin-window-policy=[deny], date=[Tue, 11 Apr 2023 09:35:55 GMT], server=[Fly/f8c3646c (2023-04-04)], x-content-type-options=[nosniff], x-download-options=[noopen], x-frame-options=[SAMEORIGIN], x-permitted-cross-domain-policies=[none], x-request-id=[F1TXukwgys8Zj38doTBh], x-runtime=[539µs], x-xss-protection=[1; mode=block], transfer-encoding=[chunked], via=[1.1 fly.io], fly-request-id=[01GXQTR04VVSDTK38KC8HNPW85-lax]]
4. exchange() 를 사용한 응답 데이터 받기
public class RestTemplateExample4 {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate =
new RestTemplate(new HttpComponentsClientHttpRequestFactory());
// (2) URI 생성
UriComponents uriComponents =
UriComponentsBuilder
.newInstance()
.scheme("http")
.host("worldtimeapi.org")
.port(80)
.path("api/timezone/{continents}/{city}")
.encode()
.build();
URI uri = uriComponents.expand("Asia", "Seoul").toUri();
// (3) Request 전송. exchange()를 사용한 일반화된 방식
ResponseEntity<WorldTime> response =
restTemplate.exchange(uri,
HttpMethod.GET,
null,
WorldTime.class);
System.out.println("datetime = " + response.getBody().getDatetime());
System.out.println("timezone = " + response.getBody().getTimezone());
System.out.println("day_of_week = " + response.getBody().getDay_of_week());
System.out.println("HTTP Status Code = " + response.getStatusCode());
System.out.println("HTTP Status Value = " + response.getStatusCodeValue());
System.out.println("Content Type = " + response.getHeaders().getContentType());
System.out.println(response.getHeaders().entrySet());
}
}
exchange(URI uri, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType)
- HTTP Method, RequestEntity, ResponseEntity를 직접 지정해서 HTTP Request를 전송할 수 있는 가장 일반적인 방식
- URI uri
- Request를 전송할 엔드포인트의 URI 객체 지정
- HttpMethod method
- HTTP Method 타입 지정
- HttpEntity<?> requestEntity
- HttpEntity 객체 지정
- HttpEntity 객체를 통해 헤더 및 바디, 파라미터 등을 설정해줄 수 있다.
- Class<T> responseType
- 응답으로 전달받을 클래스의 타입을 지정해준다.
maintenance mode가 된 RestTemplate
Spring 공식 API 문서에는 RestTemplate이 5.0 버전부터 maintenance mode 상태를 유지한다라고 명시가 되어 있습니다.
maintenance mode란 API의 사소한 변경이나 버그에 대해서는 대응을 하겠지만 신규 기능의 추가는 없을 것이라는 의미입니다. 미래에는 Deprecated될 가능성이 있다고도 볼 수 있습니다.
그런데 굳이 RestTemplate의 사용법을 배울 필요가 있을까요? 네, 있습니다. ^^
학습하기 가장 적절한 Rest Client는 여전히 RestTemplate입니다.
Spring 공식 API 문서에는 RestTemplate 대신에 WebClient라는 현대적인 API를 사용하라고 권장을 하고 있긴합니다.
그런데 WebClient는 원래 Non-Blocking 통신을 주목적으로 탄생한 Rest Client입니다.
물론 Blocking 통신을 지원하기때문에 RestTemplate 대신에 WebClient의 사용을 고려해보라고 권장하는건 맞지만 WebClient를 제대로 잘 사용하기 위해서는 Non-Blocking의 개념과 Spring WebFlux의 개념을 이해하고 난 다음에 사용하는것이 낫다고 판단됩니다.
지금은 RestTemplate을 사용해도 전혀 문제될게 없으며, 실제로 취업을 해도 기업에서 RestTemplate을 여전히 사용하고 있을 가능성이 높습니다.
정 WebClient를 사용해보고 싶다면 최소한 Spring WebFlux 유닛을 학습하고 난 이 후에 사용해 보자.
Additional Keywords
- URI scheme 목록
- Percent Encoding
- ResTemplate API
- RestTemplate API Docs
- Open API 서비스들
'Spring' 카테고리의 다른 글
| DTO 유효성 검증 (Validation) - (1) (0) | 2023.04.18 |
|---|---|
| HTTP 요청/응답에서의 DTO(Data Transfer Object) (0) | 2023.04.17 |
| [Spring MVC] API 계층 적용하기 (Prac Project - 1. 커피 주문 애플리케이션 시작) (0) | 2023.04.16 |
| [Spring MVC] API 계층 (Prac Project - 2. ResponseEntity, Header) (0) | 2023.04.16 |
| Spring MVC의 동작 방식과 구성 요소 (0) | 2023.04.14 |
블로그의 정보
Mignon'S Dev Log
mignon25