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μ μ°κ²°νλ€.
λͺ©μ°¨ κ°μ΄λ