# 새로운 HTTP 클라이언트 RestClient

Spring Framework 6.1 M2에서 [새로운 동기 HTTP 클라이언트인 `RestClient`가 출시](https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient)되었습니다! 물론 동기로 HTTP 쓰는 건 원래 `WebClient`에서도 가능했지만,

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690877157365/e7958149-52e8-4adf-86d3-79a14a80f245.png align="center")

Spring MVC에서 `WebClient` 하나 쓰기 위해 추가하던 `spring-boot-starter-webflux`를 지워버리셔도 된다는 말입니다.

따라서 이제 저희는 `RestClient`를 사용해서 `RestTemplate`의 기능들을 좀 더 가볍고 유연하게 사용할 수 있습니다.

### 💡RestClient 생성

`RestClient` 클래스의 static 메서드인 `create()`를 이용해서 생성할 수 있습니다.

```java
RestClient restClient = RestClient.create();
```

아래과 같이 기존 `RestTemplate`의 구성으로 `RestClient`를 초기화할 수 있습니다.

```java
RestClient restClient = RestClient.create(restTemplate);
```

또한 빌더 패턴을 사용해서 기본 URL, 헤더 설정 등등 추가 옵션들을 사용할 수도 있습니다.

```java
RestClient restClient = RestClient.builder()
				.baseUrl("https://www.naver.com")
				.defaultHeaders(HttpHeaders::clearContentHeaders)
				.build();
```

### 💡GET & POST 요청

아래의 코드는 RestClient로 GET 요청을 하고 사이트 내용을 문자열로 검색합니다.

```java
RestClient restClient = RestClient.create();

String result = restClient.get()
  .uri("https://example.com")
  .retrieve()
  .body(String.class);
System.out.println(result);
```

HTTP의 컨텐츠 뿐만 아니라 상태 코드나 헤더를 받고 싶다면 `ResponseEntity`를 쓸 수 있습니다.

```java
ResponseEntity<String> 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());
```

Jackson을 사용하여 JSON을 바로 객체로 변환할 수도 있습니다. 내부에서 `RestTemplate`과 똑같은 메시지 컨버터를 사용합니다.

```java
int id = ...
Pet pet = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .retrieve()
  .body(Pet.class);
```

POST 요청 또한 간단하게 다음과 같이 쓸 수 있습니다.

```java
Pet pet = ...
ResponseEntity<Void> response = restClient.post()
  .uri("https://petclinic.example.com/pets/new")
  .contentType(APPLICATION_JSON)
  .body(pet)
  .retrieve()
  .toBodilessEntity();
```

### 💡에러 핸들링

기본적으로 RestClient는 4xx 또는 5xx 상태 코드를 수신할 때 `RestClientException` 하위 클래스를 던집니다. 이 때 다음과 같은 상태 핸들러를 사용하여 재정의할 수 있습니다.

```java
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);
```

### 💡Exchange

`RestClient`는 기본 HTTP 요청 및 응답에 대한 액세스를 제공하므로 보다 고급 시나리오에 대한 `exchange()`를 제공합니다.

앞서 언급한 상태 핸들러는 exchange 기능이 이미 전체 응답에 대한 액세스를 제공하므로 필요한 오류 처리를 수행할 수 있으므로 `exchange()`를 사용할 때 적용되지 않습니다.

```java
Pet result = restClient.get()
  .uri("https://petclinic.example.com/pets/{id}", id)
  .accept(APPLICATION_JSON)
  .exchange((request, response) -> {
    if (response.getStatusCode().is4xxClientError()) {
      throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders());
    }
    else {
      Pet pet = convertResponse(response);
      return pet;
    }
  });
```

대체적으로 WebClient랑 사용법이 똑같다는 느낌이 듭니다. 근데 중요한 것은 저희가 쓸 데 없이 무거운 `spring-boot-starter-webflux`를 추가하지 않았다는 것이죠!

현재 진행 중인 개인 토이 프로젝트의 버전을 올려서 `RestTemplate` 대신 좀 더 현대적인 `RestClient`를 사용해야겠습니다.

---

### 🎯정리

* `RestTemplate`는 `Template`과 같은 클래스를 통해 모든 HTTP의 기능을 노출하는 것이 부담을 줄 수 있었습니다. 그래서 저희는 `WebClient`를 썼었습니다.
    
* `WebClient`는 `spring-boot-starter-webflux` 의존성을 추가해야 하는 부담이 있었습니다.
    
* `Spring 6.1 M2`, `Spring Boot 3.2.0-M1`부터 `RestClient`를 사용할 수 있습니다. WebFlux 의존성을 추가하지 않고도 `WebClient`처럼 너무 많은 메서드를 오버라이딩 하지 않으면서 동기적으로 HTTP 요청을 할 수 있습니다.
    

---

### 🔖참고

* [https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient](https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient)
