๐ก ์ฑ๋ด2 ์คํ๋์ค, ์์ง ํ์ผ์๋ฒ ์ฐ๋ ๊ธฐ๋ฅ ๊ตฌํ ์ค ์ธ๋ถ ์๋ฒ๋ก API ๋ฅผ ํธ์ถํ๊ธฐ ์ํด ๊ณต๋ถ
Reactive-stack ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์์ ์ํด ์ ๊ณตํ๋ ์น ํ๋ ์์ํฌReactive-stack ์น ์ ํ๋ฆฌ์ผ์ด์
: Reactive Streams API์ ๊ธฐ๋ฐํ Non-Blocking ์๋ฒ์์ ๊ตฌ๋๋๋ ์ ํ๋ฆฌ์ผ์ด์
dependencies {
compile 'org.springframework.boot:spring-boot-starter-webflux'
}
default ์ธํ /์์ฒญํ uri๋ก ํด๋ผ์ด์ธํธ๋ฅผ ๋ฐ๋ก ์์ฑํ๋ ๋ฉ์๋
WebClient์ default ์ธํ ์ผ๋ก ์๋์ ๊ฐ์ด ์์ฑํ๊ฑฐ๋, ์์ฒญํ uri๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฃ์ด์ ์์ฑ
WebClient.create();
WebClient.create("http://localhost:8080");
๋ชจ๋ ์ค์ ์ ์ปค์คํ ํ์ฌ ํด๋ผ์ด์ธํธ๋ฅผ ์์ฑํ๋ ๋ฉ์๋
Customization Options
- uriBuilderFactory: base url์ ์ปค์คํ
ํ UriBuilderFactory
- defaultUriVariables: uri ํ
ํ๋ฆฟ์ ๋ฐฐํฌํ ๋ ์ฌ์ฉํ๋ ๋ํดํธ ๊ฐ
- defaultCookie: ๋ชจ๋ ์์ฒญ์ ์ฌ์ฉํ ์ฟ ํค
- defaultHeader: ๋ชจ๋ ์์ฒญ์ ์ฌ์ฉํ ํค๋
- defaultRequest: ๋ชจ๋ ์์ฒญ์ ์ปค์คํ
ํ Consumer
- filter: ๋ชจ๋ ์์ฒญ์ ์ฌ์ฉํ ํด๋ผ์ด์ธํธ ํํฐ
- exchangeStrategies: HTTP ๋ฉ์์ง reader, writer ์ปค์คํฐ๋ง์ด์ง
- clietConnector: HTTP ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธํ
WebClient client = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
.build();
Timeout๊ณผ ๊ด๋ จ๋ ์ค์ ์ ๋ณด๋ฅผ ์ค์ ํ HttpClient ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด WebClient์ ์ฃผ์
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
์ฝ๋ ์ค๋ช : Connect Timeout๊ณผ ReadTimeout, WriteTimeout์ ๋ชจ๋ 5000ms๋ก ์ง์ ํ HttpClient๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด WebClient์ ์ฃผ์ ํ ์์.
WebClient๋ ํ ๋ฒ ๋น๋ํ ๋ค ๋ถํฐ๋ immutable ํ๋ฐ, WebClient๋ฅผ ์ฑ๊ธํด์ผ๋ก ์ฌ์ฉํ๋ ์ํฉ์์ default setting๊ณผ ๋ค๋ฅด๊ฒ ์ฌ์ฉํ๊ณ ์ถ์ ๊ฒฝ์ฐ mutate๋ฅผ ํตํด ๋ค๋ฅธ ์ค์ ๊ฐ์ ๊ฐ์ง๋ ์์ฒญ์ ํ ์ ์๊ฒ ๋๋ค.
mutable: ๊ฐ์ด ๋ณํ ์ ์๋
immutable: ๊ฐ์ด ๋ณํ์ง ์๋
client1๊ณผ client2๊ฐ ๊ฐ๋ฆฌํค๋ ๊ฐ์ฒด๋ ๊ฐ์ WebClient ์ธ์คํด์ค์ง๋ง, mutate()๋ฅผ ์ด์ฉํด ์๋ก ๋ค๋ฅธ ์ค์ ๊ฐ์ ๊ฐ์ง๋ ์์ฒญ์ ํ ์ ์๊ฒ ๋๋ค.
๊ฐ์ ๊ฐ์ฒด์ client1๊ณผ client2์ ๋ค๋ฅธ ์ต์ ๊ฐ์ ํ๋ฆฌ์ ํด๋๋ ๋๋
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
/employees
Request: collection of employees as Flux@Autowired
WebClient webClient;
public Flux<Employee> findAll() {
return webClient.get()
.uri("/employees")
.retrieve() // body๋ฅผ ๋ฐ์ ๋์ฝ๋ฉํ๋ ๋ฉ์๋
.bodyToFlux(Employee.class);
}
/employees/{id}
Request: single employee by id as Mono@Autowired
WebClient webClient;
public Mono<Employee> findById(Integer id) {
return webClient.get()
.uri("/employees/" + id)
.retrieve()
.bodyToMono(Employee.class);
}
[POST] /employees
Request: creates a new employee from request body
Response: returns the created employee
@Autowired
WebClient webClient;
public Mono<Employee> create(Employee empl) {
return webClient.post()
.uri("/employees")
.body(Mono.just(empl), Employee.class) // body์ ๋ด์ ์์ฒญํ ๋ฐ์ดํฐ์ ๊ทธ ํ์
์ ๋ช
์
.retrieve()
.bodyToMono(Employee.class); // POST ํ๊ณ ๋ฐ์ body๊ฐ ์๋ค๋ฉด ํ๋ผ๋ฏธํฐ๋ก Void.class๋ฅผ ์
๋ ฅ
}
status, headers, body๋ฅผ ํฌํจํ๋ ResponseEntity ํ์ ์ผ๋ก ๋ฐ์ ์ ์์
Mono<ResponseEntity<Employee>> entityMon = client.get();
.uri("/employee/1")
.accept(MediaType.APPLICATION_JSON)
.retreive()
.toEntity(Employee.class);
body์ ๋ฐ์ดํฐ๋ก๋ง ๋ฐ๊ณ ์ถ์ ๋ ์ฌ์ฉ
Mono<Employee> entityMono = client.get()
.uri("/employee/1")
.accept(MediaType.APPLICATION_JSON)
.retreive()
.bodyToMono(Employee.class);
Mono<Employee> entityMono = client.get()
.uri("/employee/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(resonse -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Employee.class);
} else {
return response.createException().flatMap(Mono::error);
}
})
block()์ผ๋ก blocking ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ
Mono<Employee> employeeMono = webClient.get()
/* ์ค๋ต */
employeeMono.block();
.subscribe()
๋ฉ์๋๋ฅผ ํตํด ์ฝ๋ฐฑํจ์ ์ง์
- ์ฝ๋ฐฑํจ์: ์ด๋ค ์ด๋ฒคํธ์ ์ํด ํธ์ถ๋์ด์ง๋ ํจ์
Mono<Employee> employssFlux = webClient.get()
/* ์ค๋ต */
employeeFlux.subscribe(employee -> { ... });
์๋ฌ ํธ๋ค๋ง์ ๊ฒฐ๊ณผ ๊ฐ์ ๋ฐํ๋ฐ์ ๋์ ์ํฉ์ ๋ฐ๋ผ ์ ์ ํ ์ฒ๋ฆฌํ ์ ์๋ค.
.onStatus()
์ด์ฉํด์ StatusCode ๋ณ๋ก ํธ๋ค๋ง
Mono<Employee> result = client.get()
.uri("/employee/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.retreive()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Employee.class);
exchange๋ฅผ ํตํด ๋ฐ์์๋ต์ ๋ํ statusCode๋ฅผ ์ด์ฉํด ๋ถ๊ธฐ์ฒ๋ฆฌํ์ฌ ํธ๋ค๋ง
Mono<Object> enriryMono = client.get()
.uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(Employee.class);
} else if (response.statusCode().is4xxClientError()){
return response.bodyToMono(ErrorContainer.class);
} else {
return Mono.error(response.createException());
}
})
๋นํจ์จ์ ์ธ Blocking ์ฝ๋๋ฅผ WebClient๋ฅผ ํตํด ๊ฐ์ ํ๊ธฐ
[Spring] WebFlux์ ๊ฐ๋ / Spring MVC์ ๊ฐ๋จ๋น๊ต
[WebFlux] Mono์ Flux ๊ฐ๋ ๊ณผ ๊ตฌํ
๋นํจ์จ์ ์ธ Blocking ์ฝ๋๋ฅผ WebClient๋ฅผ ํตํด ๊ฐ์ ํ๊ธฐ