Spring Web Reactive | 2. WebClient | 2.1. 구성
WebClient
를 만드는 가장 쉬운 방법은 정적 팩토리 메소드 중 하나를 사용하는 것이다.
WebClient.create()
WebClient.create(String baseUrl)
추가 옵션과 같이 WebClient.builder()
을 사용할 수 있다.
uriBuilderFactory
: 기본 URL로 사용하기 위해 커스터마이징한UriBuilderFactory
.defaultUriVariables
: URI 템플릿을 배포 할 때 사용하는 디폴트값.defaultHeader
: 모든 요청에 사용되는 헤더.defaultCookie
: 모든 요청에 사용되는 쿠키.defaultRequest
:Consumer
에 모든 요청을 사용자 지정.filter
: 모든 요청의 클라이언트 필터.exchangeStrategies
: HTTP 메시지 reader/writer의 사용자 지정.clientConnector
: HTTP 클라이언트 라이브러리의 설정.
예를 들면, 아래와 같다.
Java
WebClient client = WebClient.builder()
.codecs(configurer -> ... )
.build();
Kotlin
val webClient = WebClient.builder()
.codecs { configurer -> ... }
.build()
일단 구축되면, WebClient
은 불변이다. 단, 다음과 같이 복제하여 변경된 사본을 만들 수 있다.
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
코덱은 응용 프로그램의 메모리 문제를 해결하기 위해, 메모리에 데이터를 버퍼링하는 제한이 있다. 디폴트로 이는 256KB로 설정되어 있다. 이게 부족하면 다음과 같은 오류가 발생한다.
org.springframework.core.io.buffer.DataBufferLimitException : Exceeded limit on max bytes to buffer
기본 코덱 제한을 변경하려면 다음을 설정하면 된다.
Java
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
Kotlin
val webClient = WebClient.builder()
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
.build()
2.1.2. Reactor Netty
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로 배포 된 Spring MVC 응용 프로그램) globalResources=true
(기본값)에서 ReactorResourceFactory
형태의 Spring 관리 Bean을 선언하고 Reactor Netty가 글로벌이라는 것을 확인할 수 있다 . 다음 예에 표시된 것처럼 Spring ApplicationContext
이 닫히면 자원은 종료된다.
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) 자원 팩토리에서
ReactorClientHttpConnector
생성자를 사용한다. - (3) 커넥터
WebClient.Builder
에 연결한다.
시간 제한(Timeouts)
연결 시간 제한을 구성하려면 :
Java
import io.netty.channel.ChannelOption;
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
Kotlin
import io.netty.channel.ChannelOption
val httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
val webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
읽기 또는 쓰기 타임아웃을 구성하려면 :
Java
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
HttpClient httpClient = HttpClient.create()
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)));
// Create WebClient...
Kotlin
import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler
val httpClient = HttpClient.create()
.doOnConnected { conn -> conn
.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10))
}
// Create WebClient...
모든 요청의 응답 타임아웃을 설정하려면 :
Java
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(2));
// Create WebClient...
Kotlin
val httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(2));
// Create WebClient...
특정 요청의 응답 타임아웃을 설정하려면 :
Java
WebClient.create().get()
.uri("https://example.org/path")
.httpRequest(httpRequest -> {
HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
reactorRequest.responseTimeout(Duration.ofSeconds(2));
})
.retrieve()
.bodyToMono(String.class);
Kotlin
WebClient.create().get()
.uri("https://example.org/path")
.httpRequest { httpRequest: ClientHttpRequest ->
val reactorRequest = httpRequest.getNativeRequest<HttpClientRequest>()
reactorRequest.responseTimeout(Duration.ofSeconds(2))
}
.retrieve()
.bodyToMono(String::class.java)
2.1.3. Jetty
다음의 예는 Jetty HttpClient 설정을 사용자 지정하는 방법을 보여준다.
Java
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);
WebClient webClient = WebClient.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build();
Kotlin
val httpClient = HttpClient()
httpClient.cookieStore = ...
val webClient = WebClient.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build();
기본적으로 HttpClient
는 자신의 자원(Executor
, Scheduler
, ByteBufferPool
)을 작성하고, 프로세스가 종료되거나 stop()
이 호출 될 때까지 활성 상태로 유지된다.
다음의 예와 같이, JettyResourceFactory
를 Spring 관리 Bean을 선언함으로써, Jetty 클라이언트 (및 서버)의 여러 인스턴스간에 자원을 공유하고 Spring 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
에 연결한다.
2.1.4. HttpComponents
다음 예는 Apache HttpComponents HttpClient
설정을 사용자 지정하는 방법을 보여준다.
Java
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
WebClient webClient = WebClient.builder().clientConnector(connector).build();
Kotlin
val client = HttpAsyncClients.custom().apply {
setDefaultRequestConfig(...)
}.build()
val connector = HttpComponentsClientHttpConnector(client)
val webClient = WebClient.builder().clientConnector(connector).build()