Перейти к основному содержимому

Установите тайм-аут в Spring 5 Webflux WebClient

· 6 мин. чтения

1. Обзор

В Spring 5 добавлен совершенно новый фреймворк — Spring WebFlux , поддерживающий реактивное программирование в наших веб-приложениях. Для выполнения HTTP-запросов мы можем использовать интерфейс WebClient , предоставляющий функциональный API на основе Reactor Project .

В этом руководстве мы сосредоточимся на настройках времени ожидания для нашего WebClient . Мы обсудим различные методы, как правильно установить различные тайм-ауты, как глобально во всем приложении, так и специфичные для запроса.

2. Веб -клиент и HTTP-клиенты

Прежде чем мы двинемся дальше, давайте сделаем краткий обзор. Spring WebFlux включает в себя собственный клиент, класс WebClient , для выполнения HTTP-запросов реактивным способом. Для правильной работы WebClient также требуется клиентская библиотека HTTP. Spring предоставляет встроенную поддержку для некоторых из них, но Reactor Netty используется по умолчанию.

Большинство настроек, включая тайм-ауты, можно выполнить с помощью этих клиентов.

3. Настройка тайм-аутов через HTTP-клиент

Как мы упоминали ранее, самый простой способ установить различные тайм-ауты WebClient в нашем приложении — это установить их глобально с помощью базового HTTP-клиента. Это также самый эффективный способ сделать это.

Поскольку Netty является клиентской библиотекой по умолчанию для Spring WebFlux, мы рассмотрим наши примеры с использованием класса Reactor Netty HttpClient .

3.1. Тайм-аут ответа

Тайм-аут ответа — это время, которое мы ждем , чтобы получить ответ после отправки запроса. Мы можем использовать метод responseTimeout() , чтобы настроить его для клиента:

HttpClient client = HttpClient.create()
.responseTimeout(Duration.ofSeconds(1));

В этом примере мы настраиваем тайм-аут на 1 секунду. Netty по умолчанию не устанавливает время ожидания ответа.

После этого мы можем предоставить HttpClient Spring WebClient :

WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();

После этого WebClient наследует все конфигурации , предоставляемые базовым HttpClient для всех отправленных запросов.

3.2. Время соединения вышло

Время ожидания соединения — это период, в течение которого должно быть установлено соединение между клиентом и сервером . Мы можем использовать различные ключи параметров канала и метод option() для выполнения конфигурации:

HttpClient client = HttpClient.create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

// create WebClient...

Предоставленное значение указано в миллисекундах, поэтому мы настроили тайм-аут на 10 секунд. Netty по умолчанию устанавливает это значение равным 30 секундам.

Кроме того, мы можем настроить опцию keep-alive, которая будет отправлять проверки TCP, когда соединение простаивает:

HttpClient client = HttpClient.create()
.option(ChannelOption.SO_KEEPALIVE, true)
.option(EpollChannelOption.TCP_KEEPIDLE, 300)
.option(EpollChannelOption.TCP_KEEPINTVL, 60)
.option(EpollChannelOption.TCP_KEEPCNT, 8);

// create WebClient...

Итак, мы включили проверки активности для проверки после 5 минут бездействия с интервалом в 60 секунд. Мы также устанавливаем максимальное количество зондов до разрыва соединения до 8.

Когда соединение не устанавливается в заданное время или разрывается, генерируется исключение ConnectTimeoutException .

3.3. Тайм-аут чтения и записи

Тайм- аут чтения возникает, когда в течение определенного периода времени данные не читались, а тайм-аут записи — когда операция записи не может завершиться в определенное время. HttpClient позволяет настроить дополнительные обработчики для настройки этих тайм-аутов :

HttpClient client = HttpClient.create()
.doOnConnected(conn -> conn
.addHandler(new ReadTimeoutHandler(10, TimeUnit.SECONDS))
.addHandler(new WriteTimeoutHandler(10)));

// create WebClient...

В этой ситуации мы настроили подключенный обратный вызов с помощью метода doOnConnected() , где создали дополнительные обработчики. Для настройки тайм-аутов мы добавили экземпляры ReadTimeOutHandler и WriteTimeOutHandler . Мы устанавливаем их обоих на 10 секунд.

Конструкторы этих обработчиков принимают два варианта параметров. Для первого мы предоставили число со спецификацией TimeUnit , а второе преобразует данное число в секунды.

Базовая библиотека Netty предоставляет классы ReadTimeoutException и WriteTimeoutException соответственно для обработки ошибок .

3.4. Тайм-аут SSL/TLS

Тайм- аут рукопожатия — это время, в течение которого система пытается установить SSL-соединение перед остановкой операции . Мы можем установить конфигурацию SSL с помощью метода secure() :

HttpClient.create()
.secure(spec -> spec.sslContext(SslContextBuilder.forClient())
.defaultConfiguration(SslProvider.DefaultConfigurationType.TCP)
.handshakeTimeout(Duration.ofSeconds(30))
.closeNotifyFlushTimeout(Duration.ofSeconds(10))
.closeNotifyReadTimeout(Duration.ofSeconds(10)));

// create WebClient...

Как и выше, мы устанавливаем время ожидания рукопожатия на 30 секунд (по умолчанию: 10 секунд), в то время как время ожидания сброса (по умолчанию: 3 секунды) и чтения (по умолчанию: 0 секунд) для close_notify составляет 10 секунд . Все методы предоставляются интерфейсом SslProvider.Builder .

Исключение SslHandshakeTimeoutException используется при сбое рукопожатия из-за настроенного времени ожидания.

3.5. Время ожидания прокси

HttpClient также поддерживает функции прокси. Если попытка установления соединения с одноранговым узлом не завершается в течение тайм-аута прокси-сервера, попытка соединения завершается неудачно . Мы устанавливаем этот тайм-аут во время настройки proxy() :

HttpClient.create()
.proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
.host("proxy")
.port(8080)
.connectTimeoutMillis(30000));

// create WebClient...

Мы использовали connectTimeoutMillis() , чтобы установить время ожидания на 30 секунд, когда значение по умолчанию равно 10.

Библиотека Netty также реализует собственное исключение ProxyConnectException на случай любого сбоя .

4. Таймауты на уровне запроса

В предыдущем разделе мы глобально настроили разные тайм-ауты с помощью HttpClient . Однако мы также можем установить тайм-ауты для конкретных запросов ответа независимо от глобальных настроек .

4.1. Тайм-аут ответа — использование HttpClientRequest

Как и ранее, мы можем настроить тайм-аут ответа также на уровне запроса :

webClient.get()
.uri("https://foreach.com/path")
.httpRequest(httpRequest -> {
HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
reactorRequest.responseTimeout(Duration.ofSeconds(2));
});

В приведенном выше случае мы использовали метод httpRequest() WebClient для получения доступа к собственному HttpClientRequest из базовой библиотеки Netty . Затем мы использовали его, чтобы установить значение тайм-аута на 2 секунды. `` ``

Такой тип тайм-аута ответа переопределяет любой тайм-аут ответа на уровне HttpClient . Мы также можем установить для этого значения значение null , чтобы удалить любое ранее настроенное значение.

4.2. Реактивный тайм-аут — использование Reactor Core

Reactor Netty использует Reactor Core в качестве реализации Reactive Streams. Чтобы настроить другой тайм-аут, мы можем использовать оператор timeout() , предоставленный издателями Mono и Flux : ``

webClient.get()
.uri("https://foreach.com/path")
.retrieve()
.bodyToFlux(JsonNode.class)
.timeout(Duration.ofSeconds(5));

В этой ситуации TimeoutException появится, если ни один элемент не будет получен в течение заданных 5 секунд.

Имейте в виду, что лучше использовать более конкретные параметры конфигурации тайм-аута, доступные в Reactor Netty, поскольку они обеспечивают больший контроль для конкретной цели и варианта использования.

Метод timeout() применяется ко всей операции, от установления соединения с удаленным узлом до получения ответа. Он не отменяет никаких настроек, связанных с HttpClient .

5. Обработка исключений

Мы только что узнали о различных конфигурациях тайм-аута. Теперь пришло время быстро поговорить об обработке исключений. Каждый тип тайм-аута создает отдельное исключение, поэтому мы можем легко обрабатывать их с помощью Ractive Streams и блоков onError :

webClient.get()
.uri("https://foreach.com/path")
.retrieve()
.bodyToFlux(JsonNode.class)
.timeout(Duration.ofSeconds(5))
.onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("ReadTimeout"))
.onErrorReturn(SslHandshakeTimeoutException.class, new TextNode("SslHandshakeTimeout"))
.doOnError(WriteTimeoutException.class, ex -> log.error("WriteTimeout"))
...

Мы можем повторно использовать любые ранее описанные исключения и написать собственные методы обработки с помощью Reactor.

Кроме того, мы также можем добавить некоторую логику в соответствии со статусом HTTP :

webClient.get()
.uri("https://foreach.com/path")
.onStatus(HttpStatus::is4xxClientError, resp -> {
log.error("ClientError {}", resp.statusCode());
return Mono.error(new RuntimeException("ClientError"));
})
.retrieve()
.bodyToFlux(JsonNode.class)
...

6. Заключение

В этом руководстве мы узнали, как настроить тайм-ауты в Spring WebFlux на нашем веб-клиенте, используя примеры Netty.

Мы быстро обсудили различные тайм-ауты и способы их правильной установки на уровне HttpClient, а также то, как применить их к нашим глобальным настройкам. Затем мы работали с одним запросом, чтобы настроить время ожидания ответа на уровне запроса. Наконец, мы показали различные методы обработки возникающих исключений.

Все фрагменты кода, упомянутые в статье, можно найти на GitHub .