본문 바로가기

Dev/Spring Boot

Spring 새로운 HTTP 클라이언트 - RestClient

728x90

Spring 프레임워크가 제공하는 REST Request 엔드포인트

  • RestClient - synchronous client with a fluent API.
  • WebClient - non-blocking, reactive client with fluent API.
  • RestTemplate - synchronous client with template method API.
  • HTTP Interface - annotated interface with generated, dynamic proxy implementation.

RestClient

* <p><strong>NOTE:</strong> As of 6.1, {@link RestClient} offers a more modern
 * API for synchronous HTTP access. For asynchronous and streaming scenarios,
 * consider the reactive
 * {@link org.springframework.web.reactive.function.client.WebClient}.
 *
 * <p>{@code RestTemplate} and {@code RestClient} share the same infrastructure
 * (i.e. {@linkplain ClientHttpRequestFactory request factories}, request
 * {@linkplain org.springframework.http.client.ClientHttpRequestInterceptor interceptors} and
 * {@linkplain org.springframework.http.client.ClientHttpRequestInitializer initializers},
 * {@linkplain HttpMessageConverter message converters},
 * etc.), so any improvements made therein are shared as well.
 * However, {@code RestClient} is the focus for new higher-level features.
  • 대충 해석하면 이제 이것보다 더 모던한 RestClient 가 배포되었으니 그것을 사용할 것을 권유하고 앞으로 발전은 RestClient가 중점일 것이라한다. 그러므로 우리는 이제 RestTemplate 말고 RestClient 를 사용하자!
  • 또한 RestClient를 동기식 HTTP이고 비동기식이면 WebClient를 사용하라고 말한다.

생성

RestClient defaultClient = RestClient.create();

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();
  • RestClient가 자체 지원하는 create를 사용할 수도 있고 builder를 사용해 직접 커스텀할 수도 있다.

GET

ResponseEntity result = restClient.get()
  .uri("<https://example.com>")
  .retrieve()
  .toEntity(String.class);

System.out.println("Response status: " + result.getStatusCode());
System.out.println("Response headers: " + result.getHeaders());
System.out.println("Contents: " + result.getBody());
  • restClient.get():
    • restClient는 WebClient 객체입니다. 이 객체는 HTTP 요청을 보내기 위해 사용됩니다.
    • .get() 메서드는 HTTP GET 요청을 보낼 것을 지정합니다.
  • .uri("<https://example.com>"):
  • .retrieve():
    • 이 메서드는 요청을 실행하고, 응답을 받아오는 작업을 처리합니다.
    • 응답은 이 메서드 체인에서 이어지는 메서드(toEntity)로 전달됩니다.
  • .toEntity(String.class):
    • 응답을 ResponseEntity<String> 타입으로 변환합니다. String.class는 응답 본문을 문자열로 변환할 것을 지정합니다.
    • 이 메서드는 전체 HTTP 응답(상태 코드, 헤더, 본문)을 캡슐화하는 ResponseEntity 객체를 반환합니다.
    • result 변수는 이 ResponseEntity<String> 객체를 가리킵니다.

POST

Pet pet = ... 
ResponseEntity response = restClient.post() 
  .uri("<https://petclinic.example.com/pets/new>") 
  .contentType(APPLICATION_JSON) 
  .body(pet) 
  .retrieve()
  .toBodilessEntity(); 
  1. restClient.post():
    • restClient는 WebClient 객체입니다. 이 객체는 HTTP 요청을 보낼 때 사용됩니다.
    • .post() 메서드는 HTTP POST 요청을 보낼 것을 지정합니다.
  2. .uri("<https://petclinic.example.com/pets/new>"):
    • 요청을 보낼 URI를 지정합니다. 여기서는 "https://petclinic.example.com/pets/new" URL로 POST 요청을 보냅니다. 이 URL은 새로운 애완동물을 추가하는 API 엔드포인트로 가정됩니다.
  3. .contentType(APPLICATION_JSON):
    • 요청의 콘텐츠 타입을 application/json으로 설정합니다. 이것은 Pet 객체가 JSON 형식으로 직렬화되어 전송된다는 것을 의미합니다.
  4. .body(pet):
    • Pet 객체를 요청 본문으로 설정합니다. 이 객체는 JSON으로 변환되어 요청의 본문에 포함됩니다.
  5. .retrieve():
    • 이 메서드는 요청을 실제로 실행하고, 응답을 받아오는 작업을 처리합니다.
    • 응답은 이 메서드 체인에서 이어지는 메서드(toBodilessEntity)로 전달됩니다.
  6. .toBodilessEntity():
    • 이 메서드는 ResponseEntity<Void> 타입으로 응답을 반환합니다. 여기서 Void는 응답 본문이 없음을 나타냅니다.
    • response 변수는 이 ResponseEntity<Void> 객체를 가리킵니다.

오류 처리

기본적으로 4xx, 5xx 일 때 하위 클래스를 throw 하며 이 동작을 재정의 할 수 있다.

String result = restClient.get()
  .uri("<https://example.com/this-url-does-not-exist>")
  .retrieve()
  .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders())
  })
  .body(String.class);
  1. restClient.get():
    • restClient는 WebClient 객체로, HTTP 요청을 보내는 데 사용됩니다.
    • .get() 메서드는 HTTP GET 요청을 보낼 것을 지정합니다.
  2. .uri("<https://example.com/this-url-does-not-exist>"):
    • 요청을 보낼 URI를 지정합니다. 여기서는 https://example.com/this-url-does-not-exist라는 URL로 GET 요청을 보냅니다.
    • 이 URI는 존재하지 않는 경로를 나타내므로, 404 같은 4xx 오류가 발생할 가능성이 큽니다.
  3. .retrieve():
    • 이 메서드는 요청을 실제로 실행하고, 응답을 받아오는 작업을 처리합니다.
    • 응답은 이 메서드 체인에서 이어지는 메서드(onStatus, body)로 전달됩니다.
  4. .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { ... }):
    • 이 메서드는 특정 상태 코드에 따라 응답을 처리할 수 있도록 합니다.
    • HttpStatusCode::is4xxClientError는 4xx 상태 코드(클라이언트 오류)일 경우를 나타냅니다.
    • (request, response) -> { ... }는 상태 코드가 4xx일 경우 실행될 람다 함수입니다.
    • 이 람다 함수는 MyCustomRuntimeException 예외를 발생시킵니다. 예외는 응답의 상태 코드와 헤더를 인자로 전달받습니다.
  5. .body(String.class):
    • 이 메서드는 응답 본문을 String으로 변환하여 반환합니다.
    • 만약 4xx 오류가 발생하지 않으면, 정상적으로 응답 본문이 문자열로 반환되어 result 변수에 저장됩니다.
    • 4xx 오류가 발생하면 예외가 발생하고, 이 메서드 호출은 그 이전에 중단됩니다.

예외 처리

  • MyCustomRuntimeException:
    • 이 예외는 응답의 상태 코드와 헤더 정보를 포함한 사용자 정의 런타임 예외입니다.
    • 만약 서버가 4xx 상태 코드를 반환하면, MyCustomRuntimeException이 발생하여 오류 상태를 처리합니다.
  • MyCustomRuntimeException 를 정의하여 커스텀 예외처리가 가능합니다.