Java/Spring

(24.12.17) WebClient 정리

Job_E 2024. 12. 16. 23:44
  1. WebClient란?
    • 웹으로 API를 호출하기 위한 HTTP Client 모델
  2. WebClient의 특징
    • 싱글 스레드 & 비동기(Non-Blocking)
    • 각 요청은 Event Loop 내에 Job로써 등록됨
    • Event Loop는 각 Job을 제공자(Worker)에게 요청한 후 결과를 기다리지 않고 다른 Job를 처리
    • 이후 제공자로부터 Callback으로 응답(Response)이 오면 해당 결과를 요청자에게 제공
  3. WebClient의 사용 배경
    • 같은 Http Client 모듈인 RestTemplate와의 비교
      • RestTemplate : 멀티 스레드 & 동기(Blocking) (※ WebClient : 싱글 스레드 & 비동기)
        • Client가 요청 시 큐에 쌓이고, 가용한 스레드가 있으면 이에 할당되어 요청 처리
        • 각 스레드는 동기 방식이기 때문에 해당 요청의 응답이 오기 전까진 다른 요청을 처리할 수 없음
        • 문제 발생 등의 사유로 사용 불가능한 스레드가 많아지면 서비스가 현저히 느려질 수 있음
      • WebClient와의 성능 비교
        • Boot 1 : WebClient // Boot 2 : RestTemplate
        • 1000명까진 비슷하지만 동시사용자가 늘수록 Boot 2(RestTemplate)은 급격하게 느려짐
  4. WebClient의 사용
    • Dependency 추가
      //build.gradle
      
      
      //webClient 사용을 위한 Webflux 추가
          implementation 'org.springframework.boot:spring-boot-starter-webflux'
    • Method
      • create / build
        • WebClient를 생성하는 메소드
          WebClient.create();
          // WebClient.create("http://https://nid.naver.com"); // 요청할 uri와 함께 생성 가능
          
          // 또는
          WebClient.Build();
        • Create : 기본 옵션으로 WebClient 생성
        • Build : 모든 설정을 커스터마이징할 수 있도록 *DefaultWebClientBuilder* 클래스를 사용하여 WebClient 생성
          • 옵션 목록
            • UriBuilderFactory : BaseUri를 커스텀한 UriBuilderFactory
            • DefaultUriVariables : URI 매핑 시 사용할 기본 값 정의
            • defaultHeader : 모든 요청에 사용할 헤더
            • defaultCookie : 모든 요청에 사용할 쿠키
            • defaultRequest : 모든 요청의 Request를 커스터마이징 할 수 있는 설정
            • filter : 모든 요청에 사용할 필터 옵션
            • exchangeStrategies : HTTP 메시지 처리 관련 전략 지정 시 사용
            • ClientConnecter : Http Client의 라이브러리 세팅
            • 이외에도 observationRegistry와 observationConvention이 있지만 생략
          • 세팅 예시
            import org.springframework.web.reactive.function.client.WebClient;
            import java.util.Collections;
            
            WebClient client = WebClient
            	.builder()
            	.baseUrl("http://localhost:8080")
            	.defaultCookie("def_cookie", "val") // 쿠키 이름, 쿠키 값
            	.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080");
            	.build();
          • mutate()
            • 한번 WebClient를 빌드한 뒤부턴 WebClient는 immutable(수정 불가능)
            • 이 때 mutate()를 사용하여 기존 WebClient 인스턴스는 그대로 두고, 대상 WebClient 인스턴스를 복제해와서 설정을 변경하여 사용할 수 있음.
              import org.springframework.web.reactive.function.client.WebClient;
              import java.util.Collections;
              
              WebClient client = WebClient
              	.baseUrl("http://localhost:8080")
              	.defaultCookie("def_cookie", "val") // 쿠키 이름, 쿠키 값
              	.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080");
              	.build();
              	
              	
              WebClient client_2 = client.mutate()
              	.defaultCookie("def_cookie_2", "val_2") // 쿠키 이름, 쿠키 값
              	.build();
               

5. WebClient 사용 예시

  1. 요청 매핑
    // 현재 프로젝트 목적으로 개발중인 카카오 로그인 시 콜백 메서드
    
    private final KakaoLoginService kakaoLoginService;
    
    @GetMapping("/callback_kakao")
        public ResponseEntity<?> callback_kakao(@RequestParam("code") String code) {
            System.out.println("Authorize Code : "+code);
            String accessToken = kakaoLoginService.getAccessTokenFromKakao(code);
    
            System.out.println("AccessToken : " + accessToken);
            KakaoUserInfoResponseDto userInfoResponseDto = kakaoLoginService.getUserInfo(accessToken);
    
            System.out.println(userInfoResponseDto.getId());
    
            return new ResponseEntity<>(HttpStatus.OK);
        }

     
  2. 현재 프로젝트 목적으로 제작중인 Service의 getAccessTokenFromKakao 메서드를 보자.
    private String clientId;
         private final String KAUTH_TOKEN_URL_HOST;
         private final String KAUTH_USER_URL_HOST;
    
         @Autowired
        public KakaoLoginService(@Value("${spring.security.oauth2.client.registration.kakao.client-id}") String clientId) {
             this.clientId = clientId;
             KAUTH_TOKEN_URL_HOST = "https://kauth.kakao.com";
             // KAUTH_USER_URL_HOST = "https://kapi.kakao.com";
         }
    
         public String getAccessTokenFromKakao(String code) { // 여기서 code는 카카오에서 발급받은 인가코드를 의미한다. WebClient 설명에는 상관없으므로 설명 생략
             TokenResponseDto tokenResponseDto = WebClient.create(KAUTH_TOKEN_URL_HOST).post() // 1. post 메서드 선언
                     .uri(uriBuilder -> uriBuilder
                             .scheme("https")
                             .path("/oauth/token")
                             .queryParam("grant_type", "authorization_code")
                             .queryParam("client_id", clientId) // clientId : Kakao Developer를 통해 발급받은 클라이언트ID
                             .queryParam("code", code)
                             .build(true)) // https://kauth.kakao.com/oauth/token?grant_type=authorization_code&클라이언트ID=clientId&code=인가코드
                     .header(HttpHeaders.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                     .retrieve() //Response 세팅 : 위에서 작성된 요청의 결과를 추출할지 명시
                     // 각 서비스마다 Custom Exception이 있다면 onStatus 메서드에 지정하면 된다.
                     .onStatus(HttpStatusCode::is4xxClientError, clientResponse -> Mono.error(new RuntimeException("Invalid Parameter")))
                     .onStatus(HttpStatusCode::is5xxServerError, clientResponse -> Mono.error(new RuntimeException("Internal Server Error")))
                     .bodyToMono(TokenResponseDto.class)
                     .block(); // BodyToMomo를 통해 반환받은 결과를 동기적으로 처리
             return tokenResponseDto.getAccessToken();
         }

    • retrieve에 대한 추가 설명
      • retrieve() 이후에 toEntity(클래스명) 또는 bodyToMomo(클래스명.class)로 사용 가능
      • toEntity : status, headers, body를 포함한 ResponseEntity 타입으로 받을 수 있음
      • tobodyToMomo : 응답의 body만 받고 싶을 때 사용
    (※ 노션에다 작성하고 붙여넣는 방법을 사용해봤는데... 두번다신 안해야겠다. 내용에 제멋대로 이동하네)