[급하게 작성하는 RestTemplate -> WebClient 변환] Spring WebFlux WebClient 사용설명서 - WebClient Bean 생성

YouMakeMeSmile·2022년 2월 20일
1

해당 글은 spring-webmvc에서 외부 호출만을 위해 spring-webfluxWebClient를 사용하기 위한 설정에 대한 내용입니다.

MSA에서는 서비스간의 호출을 피할수 없는 숙명이다.

다음은 대표적인 MSA로 서비스중인 Amazon, Netfilx의 서비스간 API 호출 관계를 나타낸 그림이다. 대표적인 선생님 두분 모두 엄청나게 복잡한 서비스간의 호출관계를 지니고 있다. 이는 피할수 없는 문제인것 같은데 왜 현재 프로젝트에서는 타 서비스의 호출이 전혀 없으며 절대적인 호출관계의 레이어를 나누려 하는지 모르겠다. 나의 배움이 부족한것 같다.

위와 같이 다른 서비스의 호출은 필수적이지만 항상 먼저 키보드에 손이 가는 단어는 RestTemplate이다. 하지만 현재 RestTemplate는 유지 및 변경만 되고 있으며 향후 deprecated 된다는 설명이 있다.항상 먼저 RestTemplate에 손이 가는 벌써 옛날 개발자인가...

  • NOTE: As of 5.0 this class is in maintenance mode, with

  • only minor requests for changes and bugs to be accepted going forward. Please,
  • consider using the {@code org.springframework.web.reactive.client.WebClient}
  • which has a more modern API and supports sync, async, and streaming scenarios.
  • GitHub

우선 Bean으로 동록하여 사용할 WebClient를 생성하며 기본 설정들을 알아보려고 한다. 기본 설정들에 대한 간단한 내용은 스프링 문서에 친절하게 작성되어 있으며 이를 활용하여 이번 프로젝트에서 사용한 설정과 유용할 것으로 생각되는 설정을 알아보려고 한다.

  1. maxInMemorySize(문서)
    WebClient 사용시 항상 문제가 되었던 부분으로 WebClient의 기본 input stream의 버퍼 크기는 256k라고 한다. 이러한 이유 때문에 통신의 데이터가 256K 보다 큰 경우 DataBufferLimitException이 발생하였다. 상세한 내용은 maxInMemorySize doc를 확인하면 된다.

    WebClient
    	.builder()
    	.baseUrl("http://localhost:8080")
    	.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
  2. filter(문서)
    filter는 Request(요청), Response(응답)에 대해 intercept를 통해 수정을 할 수 있다.
    이를 통해서 수신한 Request(요청)Header의 값이 Forward되어야 하는경우 filter에서 복제하여 Request를 다른 서버로 전달 할 수 있으며 Request, Response에 대한 로그도 작성할 수 있다.
    ExchangeFilterFunction.ofRequestProcessor, ExchangeFilterFunction.ofResponseProcessor의 구현을 통해서 요청전과 응답후에 대해 수정이 가능하다.

    	WebClient
    	.builder()
    	.baseUrl("http://localhost:8080")
    	.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
    	.filter(ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
                      ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
                      log.info("####"+Thread.currentThread().getName());
                      log.info(clientRequest.url().toString());
                      ClientRequest.Builder from = ClientRequest.from(clientRequest);
                      for (Enumeration<?> e = servletRequestAttributes.getRequest().getHeaderNames(); e.hasMoreElements(); ) {
                          String nextHeaderName = (String) e.nextElement();
                          String headerValue = servletRequestAttributes.getRequest().getHeader(nextHeaderName);
                          if (nextHeaderName.equals("caller")) {
                              from.header(nextHeaderName, headerValue);
                          }
                          log.info(nextHeaderName + headerValue);
                      }
                      return Mono.just(from.build());
                  }).andThen(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
                      clientResponse.headers().asHttpHeaders().forEach((name, value) -> {
                          try {
                              log.info(name + new ObjectMapper().writeValueAsString(value));
                          } catch (JsonProcessingException e) {
                              e.printStackTrace();
                          }
                      });
                      return Mono.just(clientResponse);
                  })))
                  .build();

    여기서 약간의 의문점이 있는 점은 WebClientEvent Loop를 통해서 Single Thread에서 논블록킹를 구현된다고 한다.

    지금 프로젝트는 spring-webmvcTomcat를 사용하고 있으며 단순히 타 서버 호출을 위해 WebFlux 의존성을 추가하여 기본 Netty의 의존성도 추가되어 있다. 그러한 결과 서버 Threadhttp-nio-8080-execreactor-http-nio가 모두 존재하는것을 확인하였다.
    의문점은 현재 RequestContextHolderHeader값을 복제를 하여 다음 요청에도 추가해야 하는데 도대체 Thread가 어느 시점에 전환되어 동작 되는지 너무 복잡하다. 우선ExchangeFilterFunction.ofRequestProcessor 부분에서 Thread 이름을 출력해본 결과 http-nio-8080-exec으로 확인하였으며 ExchangeFilterFunction.ofResponseProcessor 부분에서는 reactor-http-nio으로 확인하여 Multi Thread를 이용하여 WebClient를 비동기 호출하는 경우 많은 공부가 필요해 보인다. baeldung - pring-webflux-concurrency


위의 두개의 설정이외에 timeout등 많은 설정들에 대한 내용은 문서에 많은 예제들과 친절한 설명으로 작성되어있으며 참고하면 될것같다.
다음 글에서는 이렇게 생성한 WebClient Bean을 이용하여 다양한 Http MethodMedia Type에 따른 요청방법에 대해 작성할 예정이다.

@Configuration
@Slf4j
public class SampleWebClientConfig {


    @Bean(name = "sampleWebClient")
    public WebClient sampleWebClient() {

        return WebClient
                .builder()
                .baseUrl("http://localhost:8080")
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
                .filter(ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
                    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
                    log.info("####"+Thread.currentThread().getName());
                    log.info(clientRequest.url().toString());
                    ClientRequest.Builder from = ClientRequest.from(clientRequest);
                    for (Enumeration<?> e = servletRequestAttributes.getRequest().getHeaderNames(); e.hasMoreElements(); ) {
                        String nextHeaderName = (String) e.nextElement();
                        String headerValue = servletRequestAttributes.getRequest().getHeader(nextHeaderName);
                        if (nextHeaderName.equals("caller")) {
                            from.header(nextHeaderName, headerValue);
                        }
                        log.info(nextHeaderName + headerValue);
                    }
                    return Mono.just(from.build());
                }).andThen(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
                    clientResponse.headers().asHttpHeaders().forEach((name, value) -> {
                        try {
                            log.info(name + new ObjectMapper().writeValueAsString(value));
                        } catch (JsonProcessingException e) {
                            e.printStackTrace();
                        }
                    });
                    return Mono.just(clientResponse);
                })))
                .build();
    }
}
profile
알고싶고 하고싶은게 많은 주니어 개발자 입니다.

0개의 댓글