관리 메뉴

bright jazz music

WebClient 01 : GET 본문

Framework/Spring

WebClient 01 : GET

bright jazz music 2023. 3. 6. 13:01

1. WebClient란?

  • Spring Web Flux가 제공하는 클라이언트. HTTP 요청을 수행한다.
  • WebClient는 Reactor 기반으로 동작하는 API이다. 리액터 기반이므로 스레드와 동시성 문제를 벗어나 비동기 형식으로 사용할 수 있다.

 

2. WebClient의 특징

  • 논블로킹(Non-Blocking) I/O를 지원
  • Reactve Stream의 Back Pressure 지원
  • 적은 하드웨어 리소스로 동시성 지원
  • 함수형 API 지원
  • 동기, 비동기 상호작용 지원
  • 스트리밍 지원

 

3. 사용

3.1 WebClient 사용을 위한 의존성 추가

WebFlux를 빌드 설정에 추가해 줘야 한다.

//build.gradle
dependencies {

    // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

WebFlux는 클라이언트와 서버 간 리액티브 애플리케이션 개발을 지원하기 위해 스프링 5에서 새롭게 추가된 모듈이다.

 

3.2 WebClient 구현

WebClient를 생성하는 대표적인 두 가지 방법

  • create()
  • builder()
//WebClientGetService.java

package com.project.base.service;

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

@Service
public class WebClientGetService {

    public String getName(){
        //builder()를 사용한 WebClient 생성
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")   //기본 url설정
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) //헤더 설정
                .build(); //생성

        System.out.println("## webClient " + webClient.toString());

        return webClient.get()
                .uri("/api/v1/crud-api") //
                .retrieve() //요청에 대한 응답을 받았을 때 그 값을 추출하는 방법
                .bodyToMono(String.class)
                //.toEntity(String.class)
                .block();
    }

    public ResponseEntity<String> getAsObject(){
        //builder()를 사용한 WebClient 생성
        WebClient webClient = WebClient.builder()
                .baseUrl("http://localhost:9090")   //기본 url설정

                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) //헤더 설정
                .build(); //생성

        return webClient.get()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                        .path("/get-as-object").build())
                .retrieve()
                //.bodyToMono(String.class)
                .toEntity(String.class)
                .block();

    }

    public String getNameWithPathVariable() {
        //create()를 사용한 WebClient 생성

        WebClient webClient = WebClient.create("http://localhost:9090");

        ResponseEntity<String> responseEntity = webClient.get()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api/{server}/{who}")
                        .build("localhost:8080", "catnails"))
                .retrieve()
                .toEntity(String.class)
                .block();

        System.out.println("##responseEntity.getBody()" + responseEntity.getBody());
        return responseEntity.getBody();
    }

    public String getNameWithParameter(){
        WebClient webClient = WebClient.create("http://localhost:9090");

        String returnValue =  webClient.get()
                .uri(uriBuilder -> uriBuilder.path("/api/v1/crud-api")
                        .path("/param")
                        .queryParam("name", "catnails")
                        .build())
                .exchangeToMono(clientResponse -> {
                    if (clientResponse.statusCode().equals(HttpStatus.OK)) {
                        return clientResponse.bodyToMono(String.class);
                    }else {
                        return clientResponse.createException().flatMap(Mono::error);
                    }
                })
                .block();

        System.out.println("## returnValue from service : " + returnValue);
        //## returnValue from service : catnails from http://localhost:9090/api/v1/crud-api

        return returnValue;
    }

}

WebClient는 우선 객체를 생성한 후 전달하는 방식으로 동작한다.

.builder()를 활용해 WebClient를 생성하고 defaultHeader()를 사용하여 WebClient의 기본 헤더값을 설정한다.

 

  • defaultHeader() : WebClient의 기본 헤더 설정
  • defaultCookie() : WebClient의 기본 쿠키 설정
  • defaultUriVariable() : WebClient의 기본 URI 확장값 설정
  • filter() : WebClient에서 발생하는 요청에 대한 필터 설정

 

  • retrieve() : 요청에 대한 응답에서 그 값을 추출하는 방법 중 하나
  • bodyToMono(리턴 타입) : 리턴 타입인 String.class을 넣으면 문자열을 받아온다.
  • toEntity(리턴 타입) : 리턴 타입을 설정하여 ResponseEntity 타입으로 응답을 받을 수 있다.
  • block() : blocking 방식으로 동작하도록 설정한다.
  • queryParam() : 파라미터를 요청에 담는다.
  • exchange() : 응답 결과 코드에 따라 응답을 다르게 설정할 수 있다. deprecated 되었기 때문에 exchangeToFlux()를 사용해야 한다.

 

-WebClient는 기본적으로 non-blocking 방식으로 동작한다. 이를 blocking 방식으로 동작하도록 하려면 block() 메소드를 사용한다.

 

-uri() 메서드 내부에서 uriBuilder를 사용해 path 설정 후 build() 메서드에 추가 값을 넣어서 PathVariable을 추가한다. 

 

-빌드된 WebClient는 변경 불가. 아래와 같이 복사해서 사용은 가능.

//webClient는 미리 생성한 WebClient 객체
WebClient Clone = webClient.mutate().build();

 

4. WebClientController 생성( 서비스와 연결하기 위해)

//WebClientController.java

package com.project.base.controller;

import com.project.base.service.WebClientGetService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/web-client")
public class WebClinetController {

    private final WebClientGetService webClientGetService;

    public WebClinetController(WebClientGetService webClientGetService) {
        this.webClientGetService = webClientGetService;
    }


    @GetMapping("/get-name")
    public String getName(){
        //String 값 반환
        return webClientGetService.getName();
    }

    @GetMapping("/get-as-object")
    public ResponseEntity<String> getAsObject(){
        //String 값 반환
        return webClientGetService.getAsObject();
    }

    @GetMapping("get-name-with-path-variable")
    public String getNameWithPathVariable(){
        return webClientGetService.getNameWithPathVariable();
    }

    @GetMapping("/get-name-with-parameter")
    public String getNameWithParameter(){
        return webClientGetService.getNameWithParameter();
    }


}

 

 

5. 요청을 받는 서버 (http://localhost:9090) 설정

//외부 API 역할을 하는 서버 => server.port=9090
package com.project.base.controller;

import com.project.base.domain.MemberDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/crud-api")
public class CrudController {

    /* GET: 파라미터가 없는 경우 */
    @GetMapping
    public String getName() {
        return "catnails from http://localhost:9090/api/v1/crud-api";
    }

    /* GET: 파라미터가 없는 경우 json으로 받기*/
    @GetMapping("get-as-object")
    public MemberDto getAsObject() {
        //return MemberDto.builder().name("catnails").email("catnails@gmail.com").organization("catnails company").build();
        return new MemberDto("catnails", "catnails@gmail.com", "catnails company");
    }

    /* GET: PathVariable을 사용하는 경우 */
    @GetMapping(value = "/{server}/{who}")
    public String getVariable (@PathVariable(required = false) String server,
                               @PathVariable(required = false) String who) {

        System.out.println("## server : " + server);
        System.out.println("## who : " + who);
        return "request from server : " + server + " who : " + who;
    }

    /* GET: RequestParameter를 사용하는 경우 */
    @GetMapping("/param")
    public String getNameWithParam(@RequestParam String name) {
        return "Hello! " + name + "!";
    }

    /* POST: RequestParameter와 RequestBody를 받는 경우. (귀찮아서 한 번에 해버리는 예시) */
    @PostMapping
    public ResponseEntity<MemberDto> getMember(@RequestBody MemberDto request, @RequestParam String name,
                                               @RequestParam String email, @RequestParam String organization) {

        System.out.println("## request.getName() = " + request.getName());
        System.out.println("## request.getEmail() = " + request.getEmail());
        System.out.println("## request.getOrganization() = " + request.getOrganization());

        MemberDto memberDto = new MemberDto(name, email, organization);

             return ResponseEntity.status(HttpStatus.OK).body(memberDto);
    }
    /* POST: 임의의 HTTP 헤더를 RequestBody와 함께 받는 경우*/
    @PostMapping(value = "/add-header")
    public ResponseEntity<MemberDto> addHeader(@RequestHeader("my-header") String header, @RequestBody MemberDto memberDto) {
        System.out.println(header);
        return ResponseEntity.status(HttpStatus.OK).header(header).body(memberDto);
    }

}

 

 

6. 호출 결과

 

 

Comments