IT/Spring

[리액티브 API] WebClient 개요와 사용법

김 정 환 2022. 7. 26. 23:49
반응형

리액티브 방식으로 응답 데이터를 사용하기 위해서는 Flux나 Mono 타입으로 데이터를 래핑해야 합니다. 스프링 3.0에서 소개된 RestTemplate의 리액티브 대안으로 스프링 5에서는 WebClient를 제공하고 있습니다. Webclient는 스프링 Webflux 라이브러리에 속해 있고, HTTP 프로토콜에서 동작하고 있습니다. WebClient는 웹 요청을 수행하기 위한 인터페이스로, 요청을 나타내고 전송하게 해주는 빌더 방식의 인터페이스를 사용합니다. WebClient를 사용하는 일반적인 패턴은 다음과 같습니다.

WebClient 인스턴스 생성(또는 빈으로 주입) -> HTTP 메소드 지정 -> URI와 헤더 지정 -> 요청 보내기 -> 응답 받기

 

몇 가지 예시로 WebClient 사용법을 살펴보겠습니다. 사용자 API로부터 사용자 ID로 사용자 정보를 가져온다고 예시를 만들었습니다. create() 메서드로 새로운 WebClient 인스턴스를 생성했습니다. 그다음에 get()과 uri()로 목적지 uri에 대한 GET 요청을 정의했습니다. retrieve() 메서드로 해당 요청을 실행했습니다. 마지막으로 bodyToMono()를 호출하여 응답의 페이로드를 Mono<User>로 추출했습니다. 그리고 해당 요청을 전송하기 위해서 subscribe()를 호출하여 구독을 합니다.

Mono<User> user = WebClient.create()
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .retrieve() // 요청 보내기
    .bodyToMono(User.class); // 요청 받기
    
user.subscribe(i -> {...})

 

요청을 전송하기 전에 추가적인 오퍼레이션(filter, map 등)을 적용할 수 있습니다. 아래와 같이 timeout() 메서드를 사용할 수 있습니다. Flux를 구독하기 전에 경과 시간을 1초로 지정하는 timeout()을 호출했습니다. 1초보다 작업이 오래 걸리면 타임아웃이 되어 에러 핸들러가 호출됩니다.

Flux<User> Users = WebClient.create()
    .get()
    .uri("http://localhost:8080/users")
    .retrieve()
    .bodyToMono(User.class);
    
users
    .timeout(Duration.ofSeconds(1))
    .subscribe(
        i -> {...},
        e -> { // handle timeout error }
    );

 

또는, 요청에 body() 메서드로 바디 값을 넣어서 POST로 전송할 수도 있습니다.

Mono<User> userMono = ...;
Mono<User> result = webClient
    .post()
    .uri("http://localhost:8080/users")
    .body(userMono, User.class)
    .retrieve()
    .bodyToMono(User.Class);

result.subscribe();

 

 

 

 

지금까지는 WebClient가 해피 엔딩으로 마무리 되었습니다. 그러나 가끔 에러가 슬픈 상황을 만들기 때문에 이 에러 상황을 처리해야 합니다. 에러가 생길 수 있는 Mono나 Flux를 구독할 때는 subscribe() 메서드에서 데이터 컨슈머 뿐만 아니라 에러 컨슈머도 등록해야 합니다. 아래 예시를 통해 에러 처리 방법을 보겠습니다. ID와 일치하는 사용자를 찾으면 subscribe()의 첫 번째 인자로 전달 됩니다. 그렇지 못한 경우에는 두 번째 인자로 전달됩니다. 이 경우에는 기본적으로 WebClientResponseException을 발생시킵니다. 이는 구체적으로 어떤 에러인지 모르기 때문에 에러 헨들러를 만들어 커스텀할 필요가 있습니다.

Mono<User> userMono = webClient
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .retrieve()
    .bodyToMono(User.class);
    
userMono.subscribe(
    user -> { 사용자 데이터 처리},
    error -> { 에러 처리 }
);

 

onStatus()를 사용하면 HTTP 상태 코드를 우리가 선택한 Throwable로 변환할 수 있습니다. onStatus()의 첫 번째 인자는 HttpStatus를 지정합니다. 지정한 상태 코드와 일치하면 두 번째 인자의 함수로 응답이 반환되고 이 함수에서는 Throable 타입의 Mono를 반환합니다. 여러 HTTP 상태 코드를 처리할 필요가 있을 때는 onStatus() 호출을 여러 번 할 수 있습니다.

Mono<User> userMono = webClient
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .retrieve()
    .onStatus(HttpStatus::is4xxClientError,
        response -> Mono.just(new UnknownUserException()))
    .bodyToMono(User.class);

 

 

 

 

지금까지 retrieve()를 사용하여 요청의 전송을 나타냈습니다. retrieve()는 ResponseSpec 타입의 객체를 반환하였고, 이를 통해 onStatus(), bodyToMono(), bodyToFlux()와 같은 메서드를 사용하였습니다. 그런데 ResponseSpec 객체로는 응답 헤더나 쿠키를 다룰 수 없습니다. 이럴 때에는 exchange() 를 사용하여 응답 데이터를 처리할 수 있습니다. exchange()는 ClientResponse 타입의 Mono를 반환합니다. ClientResponse 타입은 리액티브 오퍼레이션(flatMap, map, 등)을 사용할 수 있고, 응답의 모든 부분(헤더, 페이로드, 쿠키 등)에서 데이터를 사용할 수 있습니다. 

 

아래 예를 통해서 retrieve()를 exchange()로 바꿔 보겠습니다. exchange() 이후 flatMap()을 사용하여 Mono<ClientResponse>를 가져와서 ClientResponse를 Mono<User>와 연관시킵니다.

Mono<User> userMono = webClient
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .retrieve()
    .bodyToMono(Ingredient.class);
    

Mono<User> userMono = webClient
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .exchange()
    .flatMap(cr -> cr.bodyToMono(User.class));

 

다른 예시로 exchange()를 더 활용해 보겠습니다. 요청의 응답에서 "Brand"라는 해더에 값이 "Hyundai"이면, Mono<ClientResponse>를 반환해준다.

Mono<User> userMono = webClient
    .get()
    .uri("http://localhost:8080/users/{id}", userId)
    .exchange()
    .flatMap(cr -> {
        if(cr.headers().header("Brand").contains("Hyundai")){
            return Mono.just(cr);
        }
        return Mono.empty();
    })
    .flatMap(cr -> cr.bodyToMono(User.class));

 

 

 

끝.

반응형

'IT > Spring' 카테고리의 다른 글

[spring core] Databuffer  (0) 2022.08.20
[리액티브 API] 비동기 웹 프레임워크, WebFlux  (0) 2022.07.26
[리액터] 리액터 사용하기  (0) 2022.07.11
[리액터] 리액터 개요  (0) 2022.07.11
Spring boot와 MariaDB를 JPA로 연동  (0) 2022.03.20