RestClient는 동기식 HTTP Client이다. Spring 6.1, Spring Boot 3.2 버전에서 추가되었다.
생성하기
생성은 RestClient.create()
혹은 RestClient.builder().build()
를 호출하여 생성할 수 있다.
RestClient defaultClient = RestClient.create(); // RestClient.builder().build()와 같다.
좀 더 세밀한 제어를 하고싶다면, 메서드 체이닝을 통해 설정 값들을 추가할 수 있다.
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
requestFactory
- timeout 설정 제어messageConverters
- 메시지 컨버터baseUrl
- 요청 보낼 base 주소. restClient를 사용하여 요청 전송시 prefix로 붙게 된다.defaultUriVariables
- 주소 내 path variable이 존재하는 경우 기본 값을 설정할 수 있다.defaultHeader
- 인증 토큰같은 기본적으로 필요한 헤더를 세팅한다.
timeout 추가하기
다른 서비스와 통신할 때 타임아웃 설정은 필수이다. RestClient 생성 시점에 SimpleClientHttpRequestFactory
를 통해 타임아웃 설정을 전달한다.
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(Duration.ofSeconds(1));
requestFactory.setReadTimeout(Duration.ofSeconds(2));
RestClient restClient = RestClient.builder()
.requestFactory(requestFactory)
.build();
- connection timeout을 1초, read timeout을 2초로 설정한 requestFactory를 전달한다.
에러 핸들링
defaultStatusHandler()
메서드를 사용한다.
첫 번째 인자로 어떤 경우에 핸들러를 적용할지, 두 번째 인자로 정의한 핸들러를 전달한다.
RestClient restClient = RestClient.builder()
.defaultStatusHandler(
statusCode -> statusCode.is4xxClientError() || statusCode.is5xxServerError(),
(request, response) -> {
if (response.getStatusCode().is4xxClientError()) {
throw new RuntimeException("Client exception");
}
if (response.getStatusCode().is5xxServerError()) {
throw new RuntimeException("Server exception");
}
throw new RestClientException("Unexpected response status: " + response.getStatusCode());
}
)
.build();
Spring Bean으로 등록하기
RestClient 객체는 생성되고 나면 멀티 스레드 환경에서 안전하게 사용할 수 있다.
멀티 스레드에서 안전하게 사용 가능하기에, Spring Bean으로 등록해 사용해도 된다.
timeout 설정, base url, 에러 핸들링 등을 추가한 형태는 이렇다.
@Configuration
public class JavaRestClientConfig {
private static final int CONNECTION_TIMEOUT_SECONDS = 1;
private static final int READ_TIMEOUT_SECONDS = 5;
private static final Logger log = LoggerFactory.getLogger(JavaRestClientConfig.class);
@Bean
public RestClient javaRestClient(RestClient.Builder restClientBuilder) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(Duration.ofSeconds(CONNECTION_TIMEOUT_SECONDS));
requestFactory.setReadTimeout(Duration.ofSeconds(READ_TIMEOUT_SECONDS));
return restClientBuilder
.requestFactory(requestFactory)
.defaultStatusHandler(
statusCode -> statusCode.is4xxClientError() || statusCode.is5xxServerError(),
(request, response) -> {
log.error("HTTP request failed.");
log.error("Request: {} {}", request.getMethod(), request.getURI());
log.error("Response: {} {}", response.getStatusCode(), response.getStatusText());
if (response.getStatusCode().is4xxClientError()) {
throw new RuntimeException("Client exception");
}
if (response.getStatusCode().is5xxServerError()) {
throw new RuntimeException("Server exception");
}
throw new RestClientException("Unexpected response status: " + response.getStatusCode());
}
)
.build();
}
}
RestClient 사용하기
HTTP 요청 보내기
HTTP method를 선택해서 요청을 보낼 수 있다.
아래는 POST 요청 예시이다.
String response = restClient.post()
.uri("https://sang5c.tistory.com/posts")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON)
.body(new Foo("tester", "25"))
.retrieve() // retrieve 이전까지 요청에 대한 값 제어
.body(String.class); // 응답 Body 변환
아래는 GET 요청에 대한 예시이다. 경로 변수를 두고 값을 전달할 수 있고, toEntity()
를 사용하면 응답을 ResponseEntity로 받아 header나 status code에 대한 접근도 가능하다.
ResponseEntity<String> responseEntity = restClient.get()
.uri("https://sang5c.tistory.com/posts/{id}", 100)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String.class);
에러 핸들링
사용 시점에서도 제어 가능하다. onStatus()
를 사용한다.
String responsBody = client.get()
.uri("https://sang5c.tistory.com/posts/{id}", 100)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new RuntimeException("Client exception. " + response.getStatusCode());
})
.body(String.class);