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 .