본문으로 건너뛰기

WebClient

1 WebClient

  • Spring WebFlux가 제공하는 HTTP 요청을 보낼 수 있는 클라이언트

1.1 dependency

implementation 'org.springframework.boot:spring-boot-starter-webflux'

1.2 WebClient 인터페이스

package org.springframework.web.reactive.function.client;

public interface WebClient {
// Static, factory methods

/**
* Create a new {@code WebClient} with Reactor Netty by default.
* @see #create(String)
* @see #builder()
*/
static WebClient create() {
return new DefaultWebClientBuilder().build();
}

/**
* Variant of {@link #create()} that accepts a default base URL. For more
* details see {@link Builder#baseUrl(String) Builder.baseUrl(String)}.
* @param baseUrl the base URI for all requests
* @see #builder()
*/
static WebClient create(String baseUrl) {
return new DefaultWebClientBuilder().baseUrl(baseUrl).build();
}

// Static, factory methods

/**
* Create a new {@code WebClient} with Reactor Netty by default.
* @see #create(String)
* @see #builder()
*/
static WebClient create() {
return new DefaultWebClientBuilder().build();
}

/**
* Variant of {@link #create()} that accepts a default base URL. For more
* details see {@link Builder#baseUrl(String) Builder.baseUrl(String)}.
* @param baseUrl the base URI for all requests
* @see #builder()
*/
static WebClient create(String baseUrl) {
return new DefaultWebClientBuilder().baseUrl(baseUrl).build();
}
...
}

2 Configuration

static factory method로 WebClient 만들기

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

WebClient.builder() 사용해서 WebClient 만들기

WebClient client = WebClient.builder()
.codecs(configurer -> ... )
.build();
WebClient client1 = WebClient.builder()
.filter(filterA)
.filter(filterB)
.build();

3 retrieve()

  • retrieve() 메서드를 통해 response를 어떻게 뽑아낼 지 지정함
  • exchange() 메서드의 간단 버전

3.1 ResponseEntity 반환받기

  • response로 status code, headers, body를 가진 ResponseEntity를 반환 받는다
WebClient client = WebClient.create("https://example.org");

Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Person.class);

3.2 Body만 반환받기

  • response의 body만 뽑아내고 싶다면 bodyToMono 메서드를 이용하자
WebClient client = WebClient.create("https://example.org");

Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);

3.3 에러 핸들링

  • 기본적으로 response의 status가 4XX, 5XX 이라면 WebClientResponseException이 발생한다
  • status 마다 커스텀한 에러 핸들링을 하고 싶다면 아래와 샅이 onStatus 메서드를 사용하자
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);

4 exchange()

Synchronous Use

Person person = client.get().uri("/person/{id}", i).retrieve()
.bodyToMono(Person.class)
.block();
List<Person> persons = client.get().uri("/persons").retrieve()
.bodyToFlux(Person.class)
.collectList()
.block();

5 Request Body

5.1 Form Data

  • 폼 데이터를 전송하려면 바디로 MultiValueMap<String, String>를 제공하면 된다
  • 그러면 FormHttpMessageWriter가 자동적으로 application/x-www-form-urlencoded 타입의 바디를 만들어준다

예시

MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);

6 Testing

  • 외부 서비스를 호출하다 보니 목으로 대체하는 경우가 많다
  • Mockito를 사용해 WebClient를 모킹하는 것은 매우 번거롭다
    • fluent API를 모킹하면 매우 장황해짐

6.1 Mockito 사용

  • 아래 예시와 같이 모킹 과정이 상당히 복잡하다
    @Test
void givenEmployeeId_whenGetEmployeeById_thenReturnEmployee() {

Integer employeeId = 100;
Employee mockEmployee = new Employee(100, "Adam", "Sandler",
32, Role.LEAD_ENGINEER);
when(webClientMock.get())
.thenReturn(requestHeadersUriSpecMock);
when(requestHeadersUriMock.uri("/employee/{id}", employeeId))
.thenReturn(requestHeadersSpecMock);
when(requestHeadersMock.retrieve())
.thenReturn(responseSpecMock);
when(responseMock.bodyToMono(Employee.class))
.thenReturn(Mono.just(mockEmployee));

Mono<Employee> employeeMono = employeeService.getEmployeeById(employeeId);

StepVerifier.create(employeeMono)
.expectNextMatches(employee -> employee.getRole()
.equals(Role.LEAD_ENGINEER))
.verifyComplete();
}

6.2 MockWebServer 사용

  • MockWebServer는 HTTP 요청을 받고 응답을 보내는 작은 웹 서버다
  • Test 코드에서 실행되는 Webclient가 MockWebServer와 실제로 상호작용하며 HTTP 요청과 응답을 주고 받는다
  • 스프링 팀은 통합 테스트에 MockWebServer를 사용하길 권장한다

참고

참고