RestTemplate은 별도의 유틸리티 클래스를 생성하거나, 서비스 또는 비즈니스 계층에서 구현된다
앞서 생성한 서버 프로젝트에 요청을 날리기 위해 서버의 경할을 수행하면서 달느 서버로 요청을 보내는 클라이언트의 역할도 수행하는 새로운 프로젝트를 생성해야 한다
위에 구현한 서버 프로젝트는 서버2가 된다
RestTemplate은 spring-boot-starter-web 모듈에 포함되어 있는 기능으로 별도의 의존성 추가는 필요가 없다
GET 형식의 RestTemplate 작성
@Service
public class RestTemplateService {
public String getName(){
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithPathVariable(){
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/{name}")
.encode()
.build()
.expand("Flature") // 복수의 값을 넣어야 할 경우 ,를 추가하여 구분
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
public String getNameWithParameter(){
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/param")
.queryParam("name", "Flature")
.encode()
.build()
.toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);
return responseEntity.getBody();
}
}
RestTemplate을 생성 시 보통 UriComponentsBuilder를 사용한다
파라미터를 연결하여 URI를 만듬
fromUriString() 에서 호출부의 URL을 입력한다
path() 메서드에 세부 경로를 입력한다
encode() 메서드에서는 인코딩 문자셋을 설정하는데 UTF-8이 기본값이다
build() 메서드를 통해 빌더를 생성을 종료하고 UriComponents 타입이 리턴된다
이것을 toURI 메서드를 통해 URI 타입으로 리턴 받음
URI 객체를 사용하지 않고 String 타입의 URI를 사용한다면 toUriString() 메서드로 대체해서 사용하면 된다
생성한 URI는 외부 API를 요청하는데 사용된다
getForEntity() 의 파라미터로 사용하였다
path() 메서드 내에 입력한 세부 URI 중 중괄호 ({}) 부분을 사용해 개발 단계에서 쉽게 이해할 수 있는 변수명을 입력하고 expand() 메서드에서 순서대로 값을 입력하면 된다
여러 개를 넣어야 하는 경우에는 콤마(,)로 구분해서 나열한다
queryParam 메서드를 통해 (키, 값) 형식으로 파라미터가 추가가 가능하다
POST 형식의 RestTemplate 작성
public ResponseEntity<MemberDto> postwithParamAndBody(){
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api")
.queryParam("name", "Flature")
.queryParam("email", "flature@wikibooks.co.kr")
.queryParam("organization", "WikiBooks")
.encode()
.build()
.toUri();
MemberDto memberDto = new MemberDto();
memberDto.setName("flature!!");
memberDto.setEmail("flature@gmail.com");
memberDto.setOrganization("Around Hub Studio");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.postForEntity(
uri, memberDto, MemberDto.class
);
return responseEntity;
}
public ResponseEntity<MemberDto> postWithHeader(){
URI uri = UriComponentsBuilder
.fromUriString("http://localhost:9090")
.path("/api/v1/crud-api/add-header")
.encode()
.build()
.toUri();
MemberDto memberDto = new MemberDto();
memberDto.setName("flature");
memberDto.setEmail("flature@wikibooks.co.kr");
memberDto.setOrganization("Around Hub Studio");
RequestEntity<MemberDto> requestEntity = RequestEntity
.post(uri)
.header("my-header", "wikibooks API")
.body(memberDto);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<MemberDto> responseEntity = restTemplate.exchange(
requestEntity, MemberDto.class
);
return responseEntity;
}
첫 메서드 postwithParamAndBody() 은 POST 형식으로 외부 API에 요청할 때 Body 값과 파라미터 값을 담는 두 가지 방법을 보여준다
URI uri = UriComponentsBuilder. 부분은 파라미터에 값을 추가하는 작업
그 외에 밑에 부분은 RequestBody에 값을 담는 작업이 수행된다
두 번째 메서드는 헤더를 추가하는 예제다
대부분의 외부 API는 토큰키를 받아 서비스 접근을 인증하는 방식으로 작동한다
이때 토큰값을 보통 헤더에 담는다
헤더 설정을 위해선 보통 RequestEntity를 정의해서 사용하는 방법이 가장 편하다
post() 로 URI 설정
header() 메서드에서 키 일므과 값을 설정
exchange() 메서드는 모든 형식의 HTTP 요청 생성 가능
post() 메서드 대신 다른 형식의 메서드로 정의만 하면 exchange() 메서드로 쉽게 사용할 수 있어 대부분 사용한다
Swagger 설정
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.springboot.rest"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Spring Boot Open API Test with Swagger")
.description("설명 부분")
.version("1.0.0")
.build();
}
}
컨트롤러 작성
@RestController
@RequestMapping("/rest-template")
public class RestTemplateController {
private final RestTemplateService restTemplateService;
public RestTemplateController(RestTemplateService restTemplateService){
this.restTemplateService = restTemplateService;
}
@GetMapping
public String getName(){
return restTemplateService.getName();
}
@GetMapping("/path-variable")
public String getNameWithPathVariable(){
return restTemplateService.getNameWithPathVariable();
}
@GetMapping("/parameter")
public String getNameWithParameter(){
return restTemplateService.getNameWithParameter();
}
@PostMapping
public ResponseEntity<MemberDto> postDto(){
return restTemplateService.postWithParamAndBody();
}
@PostMapping("/header")
public ResponseEntity<MemberDto> postWithHeader(){
return restTemplateService.postWithHeader();
}
}
RestTemplate 커스텀 설정
RestTemplate은 HTTPClient를 추상화하고 있다
HttpClient의 종류에 따라 기능에 차이가 다소 있지만, 가장 큰 차이는 커넥션 풀 (Connection Pool)이다
RestTemplate은 기본적으로 커넥션 풀을 지원하지 않는다 (즉, 호출할 때마다 포트를 열어 커넥션을 생성하게 되는 것이다)
TIME_WAIT 상태가 된 소켓을 다시 사용하려고 접근한다면 재사용을 못한다
이를 해결하기 위해 커넥션 풀 기능을 확성화해서 재사용 가능하도록 아파치에서 제공하는 HttpClient로 대체해서 사용하는 방식이 있다
# 아파치의 HttpClient를 사용하기 위해 의존성 추가
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
// 커스텀 RestTemplate 객체 생성 메서드
public RestTemplate restTemplate(){
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
HttpClient client = HttpClientBuilder.create()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(500)
.setMaxConnPerRoute(500)
.build();
factory.setHttpClient(httpClient);
factory.setConnectTimeout(2000);
factory.setReadTimeout(5000);
RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}
}
RestTemplate의 생성자를 보면 다음과 같이 ClientHttpRequestFactory를 매개변수로 받는 생성자가 존재한다
// RestTemplate 생성자
public RestTemplate(ClientRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
ClientHttpRequestFactory는 함수형 인터페이스 (Functional interface)로, 대표적인 구현체로서 SimpleClientHttpRequestFactory와 HttpComponentsClientHttpRequestFactory가 있다
별도 구현체를 설정해서 전달하지 않으면 HTttpAccessor에 구현돼 있는 내용에 의해 SimpleClientHttpRequestFactory를 사용하게 된다
별도 HttpComponentsClientHttpRequestFactory 객체를 생성해서 ClientHttpRequestFactory를 사용하면 timeout을 설정할 수 있다
커넥션 풀을 설정하기 위해 HttpClient를 HttpComponentsClientHttpRequestFactory에 설정 가능하다
HttpClient를 생성하는 방법
HttpClient.builder.create() 메서드 사용하기
HttpClients.custom() 메서드 사용하기
setHttpClient인자로 전달해서 설정이 가능하다
이 경우 factory 객체를 RestTemplate로 초기화하는 과정에서 인자로 전달한다
WebClient란?
일반적으로 실제 운영환경에 적용되는 어플리케이션은 정식 버전으로 출시된 스프링 부트 버전보다 낮은 경우가 많다
특히 RestTemplate을 많이 사용하지만, 최신 버전에는 지원이 중단이 되어 WebClient를 사용할 것을 권고하고 있다
Spring WebFlux는 Http 요청을 수행하는 클라이언트로 WebClient를 제공한다
WebClient는 리엑터 (Reactor) 기반으로 동작하는 API이고, 스레드와 동시성 문제를 벗어나 비동기 형식으로 사용할 수 있다
논블로킹 (Non-blocking) I/O를 지원한다
리액티브 스트림 (Reactive Streams)의 백 프레셔 (Back Pressure)를 지원한다
적은 하드웨어 리소스로 동시성을 지원한다
함수형 API를 지원한다
동기, 비동기 상호 작용을 지원한다
스트리밍을 지원한다
WebClient 구성
WebClient를 사용하려면 WebFlux 모듈에 대한 의존성을 추가해야 한다
WebFlux는 클라이언트와 서버 간 리엑티브 어플리케이션을 지원하기 위해 스프링 프레임워크 5에서 추가된 모듈이다