2.1. 구성

편집일시: 2021-04-12 14:00 조회수: 179 댓글수: 0
`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 코덱은 응용 프로그램의 메모리 문제를 해결하기 위해, 메모리에 데이터를 버퍼링하는 [제한](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-limits)이 있다. 디폴트로 이는 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() ```

이전 글 : 2. WebClient
다음 글 : 2.2. retrieve()