WebClient 간단 정리 - Kim-Taesu/study GitHub Wiki
- Reactor 기반 API를 사용하여 추가적인 쓰레드나 동시성 설정 필요 없이 비동기 로직을 선언적으로 구성할 수 있다.
- fully non-bloking을 지원한다.
-
Webclient를 생성하는 가장 간단한 방법은 static factory 메서드를 사용하는 방법이다.
WebClient.create(); WebClient.create(String baseUrl);
-
WebClient.builder()
메서드로도 생성할 수 있다.-
uriBuildeFactory : 기본 URL로 사용할 커스텀 UriBuilderFactory를 설정할 수 있다.
-
defaultUriVariables : URI template를 확장할 때 사용되는 기본 값
-
defaultHeader : 모든 요청에 대한 헤더
-
defaultCookie : 모든 요청에 대한 쿠키
-
defaultRequest : 모든 요청을 커스터마이징할 consumer
-
filter : 모든 요청에 대한 필터
-
exchangeStrategies : HTTP 메시지 읽기/쓰기 커스터마이징할 때 사용
-
clientConnector : HTTP client 라이브러리 설정
WebClient client = WebClient.builder() .codecs(configurer -> ... ) .build();
-
-
WebClient
는 immutable 하다. 하지만 clone해서 수정할 수 있다.WebClient client1 = WebClient.builder() .filter(filterA).filter(filterB).build(); WebClient client2 = client1.mutate() .filter(filterC).filter(filterD).build(); // client1 has filterA, filterB // client2 has filterA, filterB, filterC, filterD
- 코덱은 메모리 문제를 피하기 위해 메모리에서 데이터를 버퍼링하는데 제한이 있다.
- 기본 256kb로 설전된다.
- 아래 설정으로 바꿀 수 있다.
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
- response를 추출하는 방법을 선언할 수 있다.
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);
- body만 가져올 수도 있다.
WebClient client = WebClient.create("https://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
- 디코딩된 stream 형태로도 가져올 수 있다.
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
- 기본적으로 4xx, 5xx 응답은
WebClientResponseException
이 발생한다.-
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);
- 응답에 대해 더 많은 제어가 필요한 경우 사용된다.
Mono<Object> entityMono = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Person.class);
}
else if (response.statusCode().is4xxClientError()) {
// Suppress error status code
return response.bodyToMono(ErrorContainer.class);
}
else {
// Turn to error
return response.createException().flatMap(Mono::error);
}
});
- request body는
Mono
,Flux
형태의 비동기 타입으로 인코딩될 수 있다.
Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.body(personMono, Person.class)
.retrieve()
.bodyToMono(Void.class);
- 인코딩된 객체 스트림 예제
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_STREAM_JSON)
.body(personFlux, Person.class)
.retrieve()
.bodyToMono(Void.class);
- request body에 저장될 실제 객체가 있다면
bodyValue
메서드로 쉽게 변환할 수 있다.
Person person = ... ;
Mono<Void> result = client.post()
.uri("/persons/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(person)
.retrieve()
.bodyToMono(Void.class);
-
MultiValueMap<String, String>
타입의 값을 body에 저장할 수 있다. - content 타입은 자동으로
FormHttpMessageWriter
에 의해application/x-www-form-urlencoded
로 설정된다.
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);
-
BodyInserters
사용 예제
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
- multipart data를 전송하기 위해
MultiValueMap<String, ?>
타입의 값이 필요하다. -
MultipartBodyBuilder
는 multi part 요청을 준비할 수 있는 편리한 API를 제공.
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
-
각 part 데이터마다 content-type을 설정할 필요가 없다.
- 직렬화하기 위해 사용되는
HttpMessageWriter
에 따라 결정된다. -
Resource
일 경우 파일 확장자에 따라 자동으로 결정된다. - 필요한 경우 각 part 마다
MediaType
을 명시적으로 설정할 수 있다.
- 직렬화하기 위해 사용되는
-
MultipartBodyBuilder
를 Body에 저장후 요청하면 된다.
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.body(builder.build())
.retrieve()
.bodyToMono(Void.class);
-
MultiValueMap에 non-string 값(form data,
application/x-www-form-urlencoded
)이 있다면 Content-Type을 꼭multipart/form-data
로 설정하지 않아도 된다. -
BodyInserters
사용 예제
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromMultipartData("fieldPart", "value").with("filePart", resource))
.retrieve()
.bodyToMono(Void.class);
- 동기 방식으로 사용하려면 각 result에 bloking을 걸면 된다.
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();
- 여러 요청에 대한 결과를 동기적으로 확인해야 하는 경우, 각 응답을 bloking하지 않고 전체 응답이 완료될 때 까지 bloking 하는 방법이 좋다.
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
.retrieve().bodyToMono(Person.class);
Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
.retrieve().bodyToFlux(Hobby.class).collectList();
Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
Map<String, String> map = new LinkedHashMap<>();
map.put("person", person);
map.put("hobbies", hobbies);
return map;
})
.block();