스프링으로 서버를 띄우는 경우가 대부분 이지만 다른 서버로 요청을 보내 데이터를 가져오는 클라이언트의 역할을 하는 부분이 필요할 수 있다
Spring에서는 WebClient를 통해 클라이언트 요청을 보낼 수 있다

WebClient

스프링 5.0버전에 추가된 Non-Blocking 방식의 HTTP 클라이언트이다
기존의 Multi Threading Blocking 방식의 RestTemplate을 대체한다. (deprecated)

설정

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

WebClientConfig

  • WebClient 객체를 필요할 때 마다 생성할 수도 있지만 빈으로 등록해서 사용할 수 있다
  • 여러 설정을 한 객체를 빈으로 등록해 재활용 한다

  • baseUrl(url) : 요청을 보낼 URL 설정
  • defaultCookie(key, value) : 쿠키 설정
  • defaultHeader(key, value) : 헤더 설정

  • MaxInMemorySize : 어플리케이션 메모리 문제를 피하기 위해 256KB로 기본 설정된 메모리 크기를 변경할 수 있다
  • Timeout : Connetion, Read, Write 등의 타임아웃을 설정할 수 있다
  • Filter : Reqeust, Response 데이터를 조작하거나 추가작업을 필터를 통해 할 수 있다
@Slf4j
@Configuration
public class WebClientConfig {

   @Bean
   public WebClient webClient() {

      // 메모리 설정
      ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
            .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024 * 50))
            .build();

      // Timeout 설정
      HttpClient httpClient = HttpClient.create()
          .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
          .doOnConnected(conn -> conn
              .addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
              .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)))
          .headers(header -> header.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
              );

      return WebClient.builder()
          .exchangeStrategies(exchangeStrategies)
          .clientConnector(new ReactorClientHttpConnector(httpClient))
          .filter(logRequest())
          .filter(logResponse())
          .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
          .build();
  }

    // Request Log Filter
    private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            log.info("Client Request : [{}, {}]", clientRequest.method(), clientRequest.url());

            return Mono.just(clientRequest);
        });
    }

    // Response Log Filter
    private static ExchangeFilterFunction logResponse() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            log.info("Client Response : {}", clientResponse.statusCode());

            return Mono.just(clientResponse);
       });
    }
}

사용

리액터

  1. Mono : 단일 객체를 반환
  2. Flux : 여러 객체(Collection)를 반환

RequestClient

  • mutate() : immutable한 빈 객체의 설정을 변경해서 사용한다
  • get(), post() : HTTP Method
  • bodyValue(object) : Body에 요청 보낼 데이터 값을 담는다

  • onStatus() : HTTP 결과 코드에 따라 Exception 처리
  • toEntity(object) : 결과를 status, headers, body를 갖는 ResponseEntity로 반환
  • bodyToMono(object), bodyToFlux(object) : 결과 데이터 맵핑
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestClient {

    private final WebClient webClient;

    public RequestResponse request(String url, RequestRequest request) {
        return webClient.mutate()
            .baseUrl(url)     // URL 설정
            .build()
            .post()           // POST
            .accept(MediaType.APPLICATION_JSON)
            .bodyValue(request)   // Body
            .retrieve()           // 결과 획득
            .onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
                    response -> response.bodyToMono(String.class)
                          .map(body -> new RumtimeException(response.statusCode().toString())))
            .bodyToMono(StockSyncResponse.class)    // 결과데이터 맵핑
            .blockOptional();
    }
}

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/client")
public class clientController() {

    private final RequestClient requestClient;

    @PostMapping
    public RequestResponse request(String url, RequestRequest request) {
        return requestClient.request(url, reuquest);
    }
}