클라이언트와 서버의 관계
크롬이나 사파리, 익스플로러 같은 웹브라우저는 웹브라우저에서 보이는 HTML 콘텐츠를 웹서버에 요청하고, 웹 서버는 요청에 해당하는 적절한 콘텐츠를 웹브라우저에 응답으로 전달하게 된다
즉 웹브라우저는 웹서버로부터 HTML 컨텐츠를 제공받는 클라이언트가 된다
웹브라우저는 웹서버가 응답으로 전달해주는 HTML컨텐츠를 전달받아서 브라우저 내에 보여준다 여기서 서버 쪽의 콘텐츠 즉, 서버 쪽의 리소스(Resource, 자원)를 이용하는 측이 클라이언트가 된다
서버는 항상 클라이언트에게 리소스를 제공하는 역할만 하는 것이아니라 서버도 다른 서버로부터 리소스를 제공받아야 하는 경우가 많
그런데 프론트의 경우 , 웹브라우저에게는 리소스를 제공하는 입장이니까 서버가 맞지만 Frontend가 Backend에 동적인 데이터를 요청하게 된다면 이때만큼은 Frontend가 BAckend의 리소스를 이용하는 클라이언트가 되는 것이다.
클라이언트 앱을 만들기 위한 리액트 앵귤러 같은 자바스크립트 진영에는 백엔드 서버와 통신하기 위해 Axios 같은 라이브러리를 사용한다
그런데 백엔드 쪽에서도 모든 작업을 하나의 서버에서 전부 처리하는 것이 아니라 백엔드 서버 내부적으로 다른 서버에게 HTTP 요청을 전송해서 작업을 나누어 처리하는 경우가 굉장히 많다
백엔드 서버가 하나 더 추가된 그림이다 이때 백엔드 A가 프런트에게 리소스를 제공해주기 때문에 서버 역할도 하지만 백엔드A에서 백엔드 B의 리소스를 다시 이용하기 때문에 백엔드B의 리소스를 이용하는 그때만큼은 백엔드 A도 클라이언트의 역할을 하게 된다
어떤 서버가 HTTP통신을 통해서 다른 서버의 리소스를 이용한다면 그때만큼은 클라이언트의 역할을 한다
Rest Client란?
Rest Client란 말 그대로 Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리를 의미한다
샘플 애플리케이션을 만들면서 사용하고 있는 포스트맨은 UI가 갖춰진 Rest Client라 보면 된다
위 그림 같이 UI가 없는 Backend A의 애플리케이션 내부에서 Backend B의 애플리케이션에 HTTP 요청을 보내려면 UI가 없는 Rest Client 라이브러리를 사용하면 된다.
RestTemplate
Java에서 사용할 수 있는 HTTP Client 라이브러리로는 java.net.HttpURLConnection, Apache HttpComponents, OkHttp3, Netty 등이 있다.
Spring에서는 HTTP Client라이브러리 중 하나를 이용해서 백엔드 서버에 HTTP 요청을 보낼 수 있는 RestTemplate이라는 Rest Client API를 제공한다
RestTemplate을 이용하면 Rest 엔드포인트 지정, 헤더 지정, 파라미터 및 body 설정을 한 줄의 코드로 손쉽게 전송할 수 있다
RestTemplate에서 Template의 의미
PPT 템플릿처럼 RestTemplate이라는 템플릿 클래스를 이용해 java.net.HttpURLConnection, Apache HttpComponents, OkHttp3, Netty 같은 HTTP Client 라이브러리 중 하나를 유연하게 사용할 수 있다
RestTemplate객체 생성
public class RestClientExample01 {
public static void main(String[] args) {
// (1) 객체 생성
RestTemplate restTemplate =
new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
}
기본적으로 RestTemplate의 객체를 생성하기 위해서는 RestTemplate의 생성자 파라미터로 HTTP Client 라이브러리의 구현 객체를 전달해야 한다 (HttpComponentsClientHttpRequestFactory 클래스를 통해 Apache HttpComponents를 전달)
Apache HttpComponents를 사용하기 위해서는 builde.gradle의 dependencies 항목에 아래와 같이 의존 라이브러리를 추가해야 한다
dependencies {
implementation 'org.apache.httpcomponents:httpclient'
}
URI생성
RestTemplate 객체를 생성했다면 HTTP Request를 전송할 Rest 엔드포인트의 URI를 지정해주어야 한다
public class RestClientExample01 {
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)를 입력한다.
- [코드 3-18]에서는 URI의 path에서 {continents}, {city}의 두 개의 템플릿 변수를 사용하고 있다.
- 두 개의 템플릿 변수는 uriComponents.expand("Asia", "Seoul"). toUri(); 에서 expand() 메서드 파라미터의 문자열로 채워진다.
- 즉, 빌드 타임에 {continents}는 ‘Asia’, {city}는 ‘Seoul’로 변환된다.
- encode()
- URI에 사용된 템플릿 변수들을 인코딩해준다.
- 여기서 인코딩의 의미는 non-ASCII 문자와 URI에 적절하지 않은 문자를 Percent Encoding 한다는 의미
- build()
- UriComponents 객체를 생성합니다.
코드 3-18에서는 테스트를 위해서 HTTP Request 엔드포인트로 World Time API의 URI를 사용하고 있다.
World Time API: http://worldtimeapi.org
다음으로, UriComponents에 사용된 API 메서드의 기능을 보면
- expand()
- 파라미터로 입력한 값을 URI 템플릿 변수의 값으로 대체한다.
- toUri()
- URI 객체를 생성한다.
요청 전송
getForObject()를 이용한 문자열 응답 데이터 받기
public class RestClientExample01 {
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);
}
}
getForObject(URI uri, Class <T> responseType)
- 기능 설명
- getForObject() 메서드는 HTTP Get 요청을 통해 서버의 리소스를 조회한다.
- 파라미터 설명
- URI uri
- Request를 전송할 엔드포인트의 URI 객체를 지정해 준다.
- Class<T> responseType
- 응답으로 전달받을 클래스의 타입을 지정해 준다.
- 응답 데이터를 문자열로 받을 수 있도록 String.class로 지정했다.
- URI uri
getForObject()를 이용한 커스텀 클래스 타입으로 원하는 정보만 응답으로 전달받기
이렇게 문자열 형태로 전달받은 응답 데이터 중에서 원하는 데이터만 전달받고 싶으면 문자열을 조작해서 원하는 정보를 얻어야 한다
public class RestClientExample02 {
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("# datatime: " + worldTime.getDatetime());
System.out.println("# timezone: " + worldTime.getTimezone());
System.out.println("# day_of_week: " + worldTime.getDay_of_week());
}
}
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 클래스이다.
WorldTime 클래스를 사용해서 전체 응답 데이터를 모두 전달받는 것이 아니라 datetime과 timezone 정보만 전달을 받고 있다.
주의해야 할 부분은 전달받고자 하는응답 데이터의 JSON 프로퍼티 이름과 클래스의 멤버 변수 이름이 동일해야 하고 해당 멤버 변수에 접근하기 위한 getter 메서드 역시 동일한 이름이어야한다.
예를 들어, JSON 프로퍼티 이름이 ‘day_of_week’라면 클래스 멤버 변수의 이름도 ‘day_of_week’ 여야 하고, 클래스 멤버 변수의 getter 메서드 명은 ‘getDay_of_week’가 되어야 한다.
실행결과
# datatime: 2021-10-10T11:39:15.099207+09:00
# timezone: Asia/Seoul
# day_of_week: 4
getForEntity()를 사용한 Response Body(바디, 콘텐츠) + Header(헤더) 정보 전달받기
getForEntity() 사용 예
public class RestClientExample02 {
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("# datatime: " + 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());
}
}
getForEntity() 메서드를 사용해서 헤더 정보와 바디 정보를 모두 전달받고 있다.
응답 데이터는 ResponseEntity 클래스로 래핑 되어서 전달되며 예제 코드와 같이 getBody(), getHeaders() 메서드 등을 이용해서 바디와 헤더 정보를 얻을 수 있다.(응답으로 전달되는 모든 헤더 정보를 보고 싶다면 예제 코드에서 처럼 getHeaders(). entrySet() 메서드를 이용해서 확인할 수 있다.)
exchange()를 사용한 응답 데이터 받기
exchange() 메서드를 사용한 방식은 앞에서 보았던 방식들보다 조금 더 일반적인 HTTP Request 방식이다.
즉, HTTP Method나 HTTP Request, HTTP Response 방식을 개발자가 직접 지정해서 유연하게 사용할 수 있다.
public class RestClientExample03 {
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("# datatime: " + 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());
}
}
exchange() 메서드를 사용한 예제 코드이다.
- exchange(URI uri, HttpMethod method, HttpEntity <?> requestEntity, Class <T> responseType)
- 기능 설명
- getForObject(), getForEntity() 등과 달리 exchange() 메서드는 HTTP Method, RequestEntity, ResponseEntity를 직접 지정해서 HTTP Request를 전송할 수 있는 가장 일반적인 방식이다.
- 파라미터 설명
- URI url
- Request를 전송할 엔드포인트의 URI 객체를 지정해 준다.
- HttpMethod method
- HTTP Method 타입을 지정해준다.
- HttpEntity<?> requestEntity
- HttpEntity 객체를 지정해준다.
- HttpEntity 객체를 통해 헤더 및 바디, 파라미터 등을 설정해줄 수 있다.
- Class<T> responseType
- 응답으로 전달받을 클래스의 타입을 지정해 준다.
- URI url
- 기능 설명
샘플 애플리케이션에서 RestTemplate 적용 포인트
샘플 애플리케이션에서 RestTemplate을 이용할 수 있는 기능에는 뭐가 있을까요?
- 결제 서비스
우리가 이번 코스가 끝날 때까지 커피 주문에 대한 실제 결제 서비스를 연동하지는 않는다.
하지만 결제 서비스를 지원해주는 PG(Payment Gateway)사와의 API 통신에 RestTemplate을 사용할 수 있을 것이다.
- 메시징 기능
주문한 커피가 나올 경우 등 카카오톡 같은 메시지로 고객에게 메시지 알림을 전송할 필요가 있을 때, 외부의 메시징 서비스와의 HTTP 통신을 위해서 RestTemplate을 사용할 수 있을 것이다.
앞에서 언급한 기능들을 샘플 애플리케이션에 적용하지는 않지만 여러분들이 프로젝트를 수행할 경우, 외부 서비스로 제공되는 API 기능을 포함시켜서 RestTemplate을 사용해본다면 개발자로서 한층 성장할 수 있는 좋은 경험이 될 거라고 생각한다.
💡 maintenance mode가 된 RestTemplate
Spring 공식 API 문서에는 RestTemplate이 5.0 버전부터 maintenance mode 상태를 유지한다라고 명시가 되어 있다.
maintenance mode란 API의 사소한 변경이나 버그에 대해서는 대응을 하겠지만 신규 기능의 추가는 없을 것이라는 의미이다. 미래에는 Deprecated 될 가능성이 있다고도 볼 수 있다.
Spring 공식 API 문서에는 RestTemplate 대신에 WebClient라는 현대적인 API를 사용하라고 권장을 하고 있다.
그런데 WebClient는 원래 Non-Blocking 통신을 주목적으로 탄생한 Rest Client이다.
물론 Blocking 통신을 지원하기 때문에 RestTemplate 대신에 WebClient의 사용을 고려해보라고 권장하는 건 맞지만 WebClient를 제대로 잘 사용하기 위해서는 Non-Blocking의 개념과 Spring WebFlux의 개념을 이해하고 난 다음에 사용하는 것이 낫다고 판단된다.
핵심 포인트
- 웹 브라우저는 웹 서버로부터 HTML 콘텐츠를 제공받는 클라이언트 중 하나이다.
- 어떤 서버가 HTTP 통신을 통해서 다른 서버의 리소스를 이용한다면 그때만큼은 클라이언트의 역할을 한다.
- Rest Client란 Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리를 의미합니다.
- RestTemplate은 원격지에 있는 다른 Backend 서버에 HTTP 요청을 전송할 수 있는 Rest Client API이다.
- RestTemplate 사용 단계
- RestTemplate 객체를 생성한다.
- HTTP 요청을 전송할 엔드포인트의 URI 객체를 생성한다.
- getForObject(), getForEntity(), exchange() 등을 이용해서 HTTP 요청을 전송한다.
- RestTemplate을 사용할 수 있는 기능 예
- 결제 서비스
- 카카오톡 등의 메시징 서비스
- Google Map 등의 지도 서비스
- 공공 데이터 포털, 카카오, 네이버 등에서 제공하는 Open API
- 기타 원격지 API 서버와의 통신
심화 학습
- 아래 링크를 통해 URI의 scheme에 대해서 더 알아보기
- URI scheme 목록: https://en.wikipedia.org/wiki/List_of_URI_schemes
- 아래 링크를 통해 Percent-Encoding에 대해서 더 알아보기
- Percent Encoding: https://ko.wikipedia.org/wiki/퍼센트_인코딩
- 아래 링크를 통해 RestTemplate API 기능을 더 알아보기
- RestTemplate API: https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-client-access
- RestTEmplate API Docs: https://docs.spring.io/spring-framework/docs/current/javadoc-api/
- 아래 링크를 통해 RestTemplate API를 활용할만한 Open API 서비스 제공 사이트를 둘러보고 어떤 기능들을 제공하는지 확인해본 후, 향후 여러분들이 프로젝트를 수행할 경우 어떤 API 서비스를 적용할 수 있을지 생각해보기
- 공공 데이터 포털: https://www.data.go.kr/dataset/3043385/openapi.do
- 카카오 REST API: https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
- 네이버 API: https://developers.naver.com/products/intro/plan/plan.md
- 구글 API 서비스: https://console.cloud.google.com
- 공공 인공지능 API 서비스: https://aiopen.etri.re.kr/
'TIL' 카테고리의 다른 글
[Spring][JPA][EntityMapping] (0) | 2022.11.01 |
---|---|
[Spring][JPA][Persistence Context] (0) | 2022.11.01 |
[SpringMVC][HTTP헤더] (0) | 2022.10.30 |
[Spring Data JDBC][DDD] (0) | 2022.10.28 |
[도메인 엔티티 및 테이블 설계][Sample] (0) | 2022.10.27 |