[Spring] WebClient

haazz·2024년 8월 21일

spring

목록 보기
3/5
post-thumbnail

개요

이번에 프로젝트를 진행하면서 Spring Boot에서 외부 API 요청을 처리가 필요했습니다.
그래서 찾아보니 SpringBoot에서는 크게 2가지 방법으로 HTTP 요청을 보낼 수 있습니다.

HTTP 요청 방법

1. RestTemplate

Spring 3부터 제공되는 Blocking HTTP Client로 HTTP 요청을 직관적으로 작성하여 사용법이 간단합니다.
하지만 스레드 Blocking을 사용하여 하나의 스레드당 하나의 요청을 처리하는 방식으로 되어있어, 요청이 끝날 때까지 스레드가 대기하고 응답을 받아온 후에 처리하게 됩니다.
이에 따라 많은 요청을 처리할때 메모리와 스레드풀과 같은 서버의 자원을 많이 소모하고 Context Switching이 빈번하게 일어나 서버 효율성에도 영향을 미칠 수 있습니다.
Spring 5.0 이후 더 이상 주요한 업데이트가 없고 WebClient로의 전환을 권장합니다.

2. WebClient

Spring 5부터 제공되는 RestTemplate의 단점을 보완할 수 있는 Non-Blocking Reactive HTTP Client입니다.
Reactive Stream API를 기반으로 Event-Drive구조로 비동기 로직을 제공합니다.
동기식/비동기식 방식 모두 사용 가능합니다.

이러한 특징들을 종합하여 저는 WebClient를 사용하기로 하였습니다.

Code

Generic한 형태로 API 요청을 보낼 수 있게 작성하였습니다.

Config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {
    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder
                .baseUrl("localhost:8080")
                .defaultHeaders(httpHeaders -> {
                    httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
                })
                .build();
    }

}

Service

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class ApiService {
    private final WebClient webClient;

    public <T, R> Mono<R> postRequest(String uri, T body, Class<R> responseType) {
    
        return webClient.post()
                .uri(uri)
                .bodyValue(body)
                .accept(MediaType.APPLICATION_JSON)
                .exchangeToMono(response -> {
                    if (response.statusCode().is2xxSuccessful()) {
                        return response.bodyToMono(responseType);
                    }
                    else {
                        log.error("Status Code: " + response.statusCode());
                        return response.bodyToMono(String.class)
                                .flatMap(err -> {
                                    log.error("Error Message: " + err);
                                    return Mono.error(new RuntimeException());
                                });
                    }
                });
    }
}

Controller

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/v1/test")
@RequiredArgsConstructor
public class SaveController {
    private final SaveService saveService;
    private final ApiService apiService;

    @PostMapping("/api")
    public void createSavingProduct(@RequestBody MyReqDTO request) {
        Mono<MyResDTO> response =
                apiService.postRequest("/test/api", request, MyResDTO.class);
        return response.block();
    }

}

참고자료

https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html
https://gngsn.tistory.com/154

profile
Developers who create benefit social values

0개의 댓글