2.1. ์ค์ (Configuration)
WebClient๋ฅผ ์์ฑํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ์ ์ ํฉํฐ๋ฆฌ ๋ฉ์๋ ์ค ํ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
WebClient.create()WebClient.create(String baseUrl)
์ ๋ฉ์๋๋ ๊ธฐ๋ณธ ์ค์ ์ผ๋ก ๋ฆฌ์กํฐ ๋คํฐ HttpClient๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํด๋์คํจ์ค์ io.projectreactor.netty:reactor-netty๊ฐ
์์ด์ผ ํ๋ค.
๋ฌผ๋ก WebClient.builder()์ ํจ๊ป ๋ค๋ฅธ ์ต์
์ ์ฌ์ฉํ ์ ์๋ค.
uriBuilderFactory: base URL์ ์ฌ์ฉํ๊ธฐ ์ํ ์ปค์คํฐ๋ง์ด์งํUriBuilderFactorydefaultHeader: ๋ชจ๋ ์์ฒญ์ ๋ํ ํค๋defaultCookie: ๋ชจ๋ ์์ฒญ์ ๋ํ ์ฟ ํคdefaultRequest: ๋ชจ๋ ์์ฒญ์ ๋ํด ์ปค์คํฐ๋ง๋ง์ด์งํConsumerfilter: ๋ชจ๋ ์์ฒญ์ ๋ํ ํด๋ผ์ด์ธํธ ํํฐexchangeStrategies: HTTP ๋ฉ์์ง reader/writer ์ปค์คํฐ๋ง์ด์งclientConnector: HTTP ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธํ
๋ค์ ์์ ๋ HTTP ์ฝ๋ฑ์ ์ค์ ํ๋ค:
Java:
WebClient client = WebClient.builder()
.exchangeStrategies(builder -> {
return builder.codecs(codecConfigurer -> {
//...
});
})
.build();
Kotlin:
val webClient = WebClient.builder()
.exchangeStrategies { strategies ->
strategies.codecs {
//...
}
}
.build()
ํ ๋ฒ ๋ง๋ค์ด์ง WebClient ์ธ์คํด์ค๋ ๋ถ๋ณ(immutable)์ด๋ค. ํ์ง๋ง ์๋ณธ ์ธ์คํด์ค์ ์ํฅ์ ์ฃผ์ง ์๊ณ ๋ณต์ ํ์ฌ ์ค์ ์ ์์ ํ ์ ์๋ค.
๋ค์์ ๊ทธ ์์ ๋ค:
Java:
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
Kotlin:
val client1 = WebClient.builder()
.filter(filterA).filter(filterB).build()
val client2 = client1.mutate()
.filter(filterC).filter(filterD).build()
// client1 has filterA, filterB
// client2 has filterA, filterB, filterC, filterD
2.1.1. MaxInMemorySize
์คํ๋ง ์นํ๋ญ์ค๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฉ๋ชจ๋ฆฌ ์ด์๋ฅผ ํผํ๊ธฐ ์ํด ์ฝ๋ฑ์ ๋ฉ๋ชจ๋ฆฌ ๋ฒํผ ์ฌ์ด์ฆ์ ๋ํ ์ ํ(limits)์ ์ค์ ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ผ๋ก 256KB๋ก ์ค์ ๋์ด ์๋๋ฐ, ์ด ๊ฐ์ผ๋ก ์ถฉ๋ถํ ์์ฉํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ ๋ฉ์์ง๋ฅผ ๋ณผ ์ ์๋ค.
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer
์๋ ์ฝ๋ ์ํ์ ์ฌ์ฉํ์ฌ ๋ชจ๋ ๊ธฐ๋ณธ ์ฝ๋ฑ์์ ์ ํ๊ฐ์ ์ค์ ํ ์ ์๋ค:
Java
WebClient webClient = WebClient.builder()
.exchangeStrategies(builder ->
builder.codecs(codecs ->
codecs.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
)
)
.build();
Kotlin
val webClient = WebClient.builder()
.exchangeStrategies { builder ->
builder.codecs {
it.defaultCodecs().maxInMemorySize(2 * 1024 * 1024)
}
}
.build()
2.1.2. ๋ฆฌ์กํฐ ๋คํฐ(Reactor Netty)
๋ฆฌ์กํฐ ๋คํฐ ์ค์ ์ ์ปค์คํ
ํ๊ธฐ ์ํด, ๋ฏธ๋ฆฌ ์ค์ ๋ HttpClient๋ฅผ ์ ๊ณตํ๋ค:
Java:
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Kotlin:
val httpClient = HttpClient.create().secure { ... }
val webClient = WebClient.builder()
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
Resources
๊ธฐ๋ณธ์ ์ผ๋ก HttpClient๋ ์ด๋ฒคํธ ๋ฃจํ ์ค๋ ๋์ ์ปค๋ฅ์
ํ์ ํฌํจํ์ฌ reactor.netty.http.HttpResources์ ํฌํจ๋ ์ ์ญ Reactor
Netty ์์์ ์ฌ์ฉํ๋ค. ์ด๋ฒคํธ ๋ฃจํ ๋์์ฑ์๋ ๊ณต์ ์์์ ๊ณ ์ ํด๋๋ ๊ฒ์ด ์ข๊ธฐ ๋๋ฌธ์ ์ด ๋ชจ๋๋ ๊ถ์ฅ๋๋ค. ์ด ๋ชจ๋์์๋ ํ๋ก์ธ์ค๊ฐ
์ข
๋ฃ๋ ๋๊น์ง ๊ณต์ ์์์ ํ์ฑํ๋ ์ํ๋ฅผ ์ ์งํ๋ค.
์๋ฒ๊ฐ ํ๋ก์ธ์ค์ ๋ง์ถฐ์ง๋ค๋ฉด, ์ผ๋ฐ์ ์ผ๋ก ๋ช
์์ ์ผ๋ก ์
ง๋ค์ดํ ํ์๋ ์๋ค. ํ์ง๋ง ์๋ฒ๊ฐ ํ๋ก์ธ์ค ๋ด์์ ์์ํ๊ฑฐ๋ ์ค๋จ๋ ์ ์๋ค๋ฉด
(์๋ฅผ ๋ค์ด, WAR๋ก ๋ฐฐํฌ๋ ์คํ๋ง MVC ์ ํ๋ฆฌ์ผ์ด์
), ์คํ๋ง์ด ๊ด๋ฆฌํ๋ ReactorResourceFactory ๋น(bean)์
globalResources=true๋ก ์ค์ ํ์ฌ ์คํ๋ง ApplicationContext๊ฐ ๋ซํ ๋ Reactor Netty ๊ณต์ ์์์ด ์ข
๋ฃ๋๋๋ก ์ค์ ํ ์ ์๋ค.
๋ค์์ ๊ทธ ์์ ๋ค:
Java:
@Bean
public ReactorResourceFactory reactorResourceFactory() {
return new ReactorResourceFactory();
}
Kotlin:
@Bean
fun reactorResourceFactory() = ReactorResourceFactory()
๋ํ Reactor Netty ๋ฆฌ์์ค๋ฅผ ์ฌ์ฉํ์ง ์๊ฒ ์ค์ ํ ์๋ ์๋ค. ํ์ง๋ง ์ด ๋ชจ๋์์๋, ๋ค์ ์์ ์ ๊ฐ์ด ๋ชจ๋ Reactor Netty ํด๋ผ์ด์ธํธ์ ์๋ฒ ์ธ์คํด์ค๊ฐ ๊ณต์ ์์์ ์ฌ์ฉํ๋๋ก ํด์ผํ๋ ๋ถ๋ด์ด ์๋ค:
Java:
@Bean
public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false); (1)
return factory;
}
@Bean
public WebClient webClient() {
Function<HttpClient, HttpClient> mapper = client -> {
// Further customizations...
};
ClientHttpConnector connector =
new ReactorClientHttpConnector(resourceFactory(), mapper); (2)
return WebClient.builder().clientConnector(connector).build(); (3)
}
Kotlin:
@Bean
fun resourceFactory() = ReactorResourceFactory().apply {
isUseGlobalResources = false (1)
}
@Bean
fun webClient(): WebClient {
val mapper: (HttpClient) -> HttpClient = {
// Further customizations...
}
val connector = ReactorClientHttpConnector(resourceFactory(), mapper) (2)
return WebClient.builder().clientConnector(connector).build() (3)
}
(1) ์ ์ญ ์์๊ณผ ๋ ๋ฆฝ๋ ์์์ ์์ฑํ๋ค.
(2) ์์ ํฉํ ๋ฆฌ(resource factory)๋กReactorClientHttpConnector์์ฑ์๋ฅผ ์ฌ์ฉํ๋ค.
(3) ์ปค๋ฅํฐ๋ฅผWebClient.Builder์ ์ฐ๊ฒฐํ๋ค.
ํ์์์(Timeouts)
๋ค์์ ์ปค๋ฅ์ ํ์์์์ ์ค์ ํ๋ ์์ ๋ค:
Java:
import io.netty.channel.ChannelOption;
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000));
Kotlin:
import io.netty.channel.ChannelOption
val httpClient = HttpClient.create()
.tcpConfiguration { it.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)}
๋ค์์ read/write ํ์์์์ ์ค์ ํ๋ ์์ ๋ค:
Java:
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client ->
client.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10))));
Kotlin:
import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler
val httpClient = HttpClient.create().tcpConfiguration {
it.doOnConnected { conn -> conn
.addHandlerLast(ReadTimeoutHandler(10))
.addHandlerLast(WriteTimeoutHandler(10))
}
}
2.1.3. ์ ํฐ(Jetty)
๋ค์์ ์ ํฐ HttpClient ์ค์ ์ ์ปค์คํฐ๋ง์ด์งํ๋ ์์ ๋ค:
Java:
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);
ClientHttpConnector connector = new JettyClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().clientConnector(connector).build();
Kotlin:
val httpClient = HttpClient()
httpClient.cookieStore = ...
val connector = JettyClientHttpConnector(httpClient)
val webClient = WebClient.builder().clientConnector(connector).build();
๊ธฐ๋ณธ์ ์ผ๋ก HttpClient๋ ์์ ์ ๊ณ ์ ํ ์์(Executor, ByteBufferPool, Scheduler)์ ์์ฑํด์, ํ๋ก์ธ์ค๊ฐ ์ข
๋ฃ๋๊ฑฐ๋
stop()์ด ํธ์ถ๋ ๋๊น์ง ํ์ฑ ์ํ๋ฅผ ์ ์งํ๋ค.
์ ํฐ ํด๋ผ์ด์ธํธ(๋ฐ ์๋ฒ)์ ์ฌ๋ฌ ์ธ์คํด์ค ๊ฐ์ ์์์ ๊ณต์ ํ ์ ์๊ณ , JettyResourceFactory ํ์
์ ์คํ๋ง์ด ๊ด๋ฆฌํ๋ ๋น์ผ๋ก ์ ์ธํ์ฌ
์คํ๋ง ApplicationContext๊ฐ ๋ซํ ๋ ์์์ด ์ข
๋ฃ๋๋๋ก ํ ์ ์๋ค. ๋ค์์ ๊ทธ ์์ ๋ค:
Java:
@Bean
public JettyResourceFactory resourceFactory() {
return new JettyResourceFactory();
}
@Bean
public WebClient webClient() {
HttpClient httpClient = new HttpClient();
// Further customizations...
ClientHttpConnector connector =
new JettyClientHttpConnector(httpClient, resourceFactory()); (1)
return WebClient.builder().clientConnector(connector).build(); (2)
}
Kotlin:
@Bean
fun resourceFactory() = JettyResourceFactory()
@Bean
fun webClient(): WebClient {
val httpClient = HttpClient()
// Further customizations...
val connector = JettyClientHttpConnector(httpClient, resourceFactory()) (1)
return WebClient.builder().clientConnector(connector).build() (2)
}
(1)
JettyClientHttpConnector์์ฑ์์ ๋ฆฌ์์ค ํฉํ ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค.
(2) ์ปค๋ฅํฐ๋ฅผWebClient.Builder์ ์ฐ๊ฒฐํ๋ค.
๋ชฉ์ฐจ ๊ฐ์ด๋