HTTP клиент
Модуль HTTP-клиента описывает исходящие HTTP-вызовы приложения: от выбора транспортной реализации до преобразования запроса,
преобразования ответа, телеметрии и перехватчиков. В Kora можно описывать типизированные клиенты декларативно через @HttpClient
и @HttpRoute с тонким слоем абстракции, либо использовать общий интерфейс HttpClient напрямую, когда запрос нужно собрать в коде.
Декларативный подход подходит для большинства интеграций с внешними службами: контракт метода становится контрактом удаленного вызова,
а Kora во время компиляции создает реализацию без использования Reflection во время работы. Императивный подход полезен для низкоуровневых
или динамических сценариев, где путь, заголовки, параметры или тело запроса удобнее собирать вручную.
Совет
Мы советуем использовать подход, при котором первичен контракт в формате OpenAPI,
а клиенты создаются с помощью генератора.
Такой подход помогает сохранить согласованность контракта между потребителем и владельцем контракта
и быстрее обновлять клиент при изменении контракта за счет замены файла описания.
Подробнее про генератор смотрите в разделе про генерацию из OpenAPI.
Если нужен пошаговый разбор перед справочным описанием, смотрите HTTP-клиент и продвинутый HTTP-клиент.
OkHttp¶
Реализация HTTP-клиента основана на библиотеке OkHttp.
Учитывайте что реализация написана на Kotlin и использует соответствующие зависимости.
Лучше всего подходит для Kotlin сервисов, либо Java сервисов где нужна высокая производительность,
либо требуется поддержка HTTP 3, либо поддержка GZip сжатия, либо другие специфичные HTTP опции.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Конфигурация¶
Основные параметры конфигурации OkHttp клиента:
- Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m)
Полная конфигурация
Пример полной конфигурации, описанной в классе OkHttpClientConfig и HttpClientConfig (указаны примеры значений или значения по умолчанию):
httpClient {
ok {
followRedirects = true //(1)!
httpVersion = "HTTP_1_1" //(2)!
retryOnConnectionFailure = true //(3)!
}
connectTimeout = "5s" //(4)!
readTimeout = "2m" //(5)!
useEnvProxy = false //(6)!
proxy {
host = "localhost" //(7)!
port = 8090 //(8)!
user = "user" //(9)!
password = "password" //(10)!
nonProxyHosts = [ "host1", "host2" ] //(11)!
}
telemetry {
logging {
enabled = false //(12)!
mask = "***" //(13)!
maskQueries = [ ] //(14)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(15)!
pathTemplate = true //(16)!
}
metrics {
enabled = true //(17)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(18)!
tags = { // (19)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(20)!
attributes = { // (21)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
- Следовать ли по перенаправлениям в HTTP (по умолчанию:
true) - Максимальная используемая версия
HTTP-протокола, доступные значения:HTTP_1_1/HTTP_2/HTTP_3(по умолчанию:HTTP_1_1) - Пробовать ли повторно выполнить запрос при ошибке соединения; может влиять на предельное время установления соединения (по умолчанию:
true) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
httpClient:
ok:
followRedirects: true #(1)!
httpVersion: "HTTP_1_1" #(2)!
retryOnConnectionFailure: true #(3)!
connectTimeout: "5s" #(4)!
readTimeout: "2m" #(5)!
useEnvProxy: false #(6)!
proxy:
host: "localhost" #(7)!
port: 8090 #(8)!
user: "user" #(9)!
password: "password" #(10)!
nonProxyHosts: [ "host1", "host2" ] #(11)!
telemetry:
logging:
enabled: false #(12)!
mask: "***" #(13)!
maskQueries: [ ] #(14)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(15)!
pathTemplate: true #(16)!
metrics:
enabled: true #(17)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(18)!
tags: #(19)!
key1: value1
key2: value2
tracing:
enabled: true #(20)!
attributes: #(21)!
key1: value1
key2: value2
- Следовать ли по перенаправлениям в HTTP (по умолчанию:
true) - Максимальная используемая версия
HTTP-протокола, доступные значения:HTTP_1_1/HTTP_2/HTTP_3(по умолчанию:HTTP_1_1) - Пробовать ли повторно выполнить запрос при ошибке соединения; может влиять на предельное время установления соединения (по умолчанию:
true) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
Предоставляемые метрики модуля описаны в разделе Справочник метрик.
Конфигуратор¶
Пример настройки построителя OkHttp клиента, OkHttpConfigurer должен быть доступен как компонент:
AsyncHttpClient¶
Реализация HTTP-клиента основана на библиотеке Async HTTP Client.
Подходит для Java сервисов, где преобладают асинхронные вызовы.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Конфигурация¶
Основные параметры конфигурации AsyncHttpClient:
- Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m)
Полная конфигурация
Пример полной конфигурации, описанной в классе AsyncHttpClientConfig и HttpClientConfig (указаны примеры значений или значения по умолчанию):
httpClient {
async {
followRedirects = true //(1)!
}
connectTimeout = "5s" //(2)!
readTimeout = "2m" //(3)!
useEnvProxy = false //(4)!
proxy {
host = "localhost" //(5)!
port = 8090 //(6)!
user = "user" //(7)!
password = "password" //(8)!
nonProxyHosts = [ "host1", "host2" ] //(9)!
}
telemetry {
logging {
enabled = false //(10)!
mask = "***" //(11)!
maskQueries = [ ] //(12)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(13)!
pathTemplate = true //(14)!
}
metrics {
enabled = true //(15)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(16)!
tags = { // (17)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(18)!
attributes = { // (19)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
- Следовать ли по перенаправлениям в HTTP (по умолчанию:
true) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
httpClient:
async:
followRedirects: true #(1)!
connectTimeout: "5s" #(2)!
readTimeout: "2m" #(3)!
useEnvProxy: false #(4)!
proxy:
host: "localhost" #(5)!
port: 8090 #(6)!
user: "user" #(7)!
password: "password" #(8)!
nonProxyHosts: [ "host1", "host2" ] #(9)!
telemetry:
logging:
enabled: false #(10)!
mask: "***" #(11)!
maskQueries: [ ] #(12)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(13)!
pathTemplate: true #(14)!
metrics:
enabled: true #(15)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(16)!
tags: #(17)!
key1: value1
key2: value2
tracing:
enabled: true #(18)!
attributes: #(19)!
key1: value1
key2: value2
- Следовать ли по перенаправлениям в HTTP (по умолчанию:
true) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
Можно также настроить Netty транспорт.
Java клиент¶
Реализация HTTP-клиента основана на встроенном Java-клиенте, поставляемом в JDK.
Лучше всего подходит для Java-сервисов, где не требуется максимальная производительность,
и хочется минимизировать количество внешних библиотек.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Конфигурация¶
Основные параметры конфигурации JDK HttpClient:
- Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m)
Полная конфигурация
Пример полной конфигурации, описанной в классе JdkHttpClientConfig и HttpClientConfig (указаны примеры значений или значения по умолчанию):
httpClient {
jdk {
threads = 2 //(1)!
httpVersion = "HTTP_1_1" //(2)!
}
connectTimeout = "5s" //(3)!
readTimeout = "2m" //(4)!
useEnvProxy = false //(5)!
proxy {
host = "localhost" //(6)!
port = 8090 //(7)!
user = "user" //(8)!
password = "password" //(9)!
nonProxyHosts = [ "host1", "host2" ] //(10)!
}
telemetry {
logging {
enabled = false //(11)!
mask = "***" //(12)!
maskQueries = [ ] //(13)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(14)!
pathTemplate = true //(15)!
}
metrics {
enabled = true //(16)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(17)!
tags = { // (18)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(19)!
attributes = { // (20)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
- Количество потоков для
HTTP-клиента (по умолчанию: количество доступных процессоров, умноженное на2) - Какую версию
HTTP-протокола использовать, доступные значения:HTTP_1_1/HTTP_2(по умолчанию:HTTP_1_1) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
httpClient:
jdk:
threads: 2 #(1)!
httpVersion: "HTTP_1_1" #(2)!
connectTimeout: "5s" #(3)!
readTimeout: "2m" #(4)!
useEnvProxy: false #(5)!
proxy:
host: "localhost" #(6)!
port: 8090 #(7)!
user: "user" #(8)!
password: "password" #(9)!
nonProxyHosts: [ "host1", "host2" ] #(10)!
telemetry:
logging:
enabled: false #(11)!
mask: "***" #(12)!
maskQueries: [ ] #(13)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(14)!
pathTemplate: true #(15)!
metrics:
enabled: true #(16)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(17)!
tags: #(18)!
key1: value1
key2: value2
tracing:
enabled: true #(19)!
attributes: #(20)!
key1: value1
key2: value2
- Количество потоков для
HTTP-клиента (по умолчанию: количество доступных процессоров, умноженное на2) - Какую версию
HTTP-протокола использовать, доступные значения:HTTP_1_1/HTTP_2(по умолчанию:HTTP_1_1) - Максимальное время на установление соединения (по умолчанию:
5s) - Максимальное время на чтение ответа (по умолчанию:
2m) - Использовать ли переменные окружения
https_proxy/HTTPS_PROXY/http_proxy/HTTP_PROXYиno_proxy/NO_PROXYдля настройки прокси (по умолчанию:false) - Адрес прокси (
обязательная, по умолчанию не указано) - Порт прокси (
обязательная, по умолчанию не указано) - Пользователь для прокси (по умолчанию не указано, необязательно)
- Пароль для прокси (по умолчанию не указано, необязательно)
- Узлы, которые следует исключить из проксирования (по умолчанию не указано, необязательно)
- Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
Декларативный клиент¶
Предлагается использовать специальные аннотации для создания декларативного клиента:
@HttpClient— указывает, что интерфейс является декларативнымHTTP-клиентом@HttpRoute— указывает тип HTTP-запроса и путь запроса
Конфигурация клиента¶
Конфигурация конкретной реализации @HttpClient по умолчанию ищется по пути httpClient.{имя класса в нижнем регистре}.
Если нужно задать путь явно, используйте параметр configPath в аннотации:
В @HttpClient также можно указать теги для внедряемых компонентов:
httpClientTag— тег для выбора конкретного транспортногоHttpClient, если в графе есть несколько реализаций с разными@TagtelemetryTag— тег для выбора конкретной фабрики телеметрии клиента
Основные параметры конфигурации декларативного клиента:
- Базовый
URLсервиса, куда будут отправляться запросы (обязательная, по умолчанию не указано) - Максимальное время запроса (по умолчанию не указано, необязательно)
Полная конфигурация
Пример конфигурации в случае пути httpClient.someClient описанной в классе DeclarativeHttpClientConfig:
httpClient {
someClient {
url = "https://localhost:8090" //(1)!
requestTimeout = "10s" //(2)!
telemetry {
logging {
enabled = false //(3)!
mask = "***" //(4)!
maskQueries = [ ] //(5)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(6)!
pathTemplate = true //(7)!
}
metrics {
enabled = true //(8)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(9)!
tags = { // (10)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(11)!
attributes = { // (12)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
}
- Базовый
URLсервиса, куда будут отправляться запросы (обязательная, по умолчанию не указано) - Максимальное время запроса: может включать разрешение
DNS, подключение, запись тела запроса, обработку сервером и чтение тела ответа. Если вызов требует перенаправления или повторных попыток, все они должны завершиться в течение одного периода (по умолчанию не указано, необязательно) - Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
httpClient:
someClient:
url: "https://localhost:8090" #(1)!
requestTimeout: "10s" #(2)!
telemetry:
logging:
enabled: false #(3)!
mask: "***" #(4)!
maskQueries: [ ] #(5)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(6)!
pathTemplate: true #(7)!
metrics:
enabled: true #(8)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(9)!
tags: #(10)!
key1: value1
key2: value2
tracing:
enabled: true #(11)!
attributes: #(12)!
key1: value1
key2: value2
- Базовый
URLсервиса, куда будут отправляться запросы (обязательная, по умолчанию не указано) - Максимальное время запроса: может включать разрешение
DNS, подключение, запись тела запроса, обработку сервером и чтение тела ответа. Если вызов требует перенаправления или повторных попыток, все они должны завершиться в течение одного периода (по умолчанию не указано, необязательно) - Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, шаблон используется всегда, кроме уровня
TRACE, где используется полный путь (по умолчанию не указано, необязательно) - Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
Конфигурация метода¶
Для конкретного метода можно отдельно настроить часть параметров. Путь к конфигурации метода определяется путем к клиенту и именем метода:
если путь клиента httpClient.someClient, то для метода hello итоговый путь будет httpClient.someClient.hello.
Конфигурация метода накладывается поверх конфигурации клиента: requestTimeout метода заменяет клиентское значение, а настройки телеметрии метода
переопределяют только явно указанные поля.
Основные параметры конфигурации метода:
Полная конфигурация
Пример полной конфигурации метода:
httpClient {
someClient {
hello {
requestTimeout = "10s" //(1)!
telemetry {
logging {
enabled = false //(2)!
mask = "***" //(3)!
maskQueries = [ ] //(4)!
maskHeaders = [ "authorization", "cookie", "set-cookie" ] //(5)!
pathTemplate = true //(6)!
}
metrics {
enabled = true //(7)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(8)!
tags = { // (9)!
"key1" = "value1"
"key2" = "value2"
}
}
tracing {
enabled = true //(10)!
attributes = { // (11)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
}
}
- Максимальное время запроса: может включать разрешение
DNS, подключение, запись тела запроса, обработку сервером и чтение тела ответа. Если вызов требует перенаправления или повторных попыток, все они должны завершиться в течение одного периода (по умолчанию не указано, необязательно) - Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, наследуется значение клиента (по умолчанию не указано, необязательно)
- Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
httpClient:
someClient:
hello:
requestTimeout: "10s" #(1)!
telemetry:
logging:
enabled: false #(2)!
mask: "***" #(3)!
maskQueries: [ ] #(4)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(5)!
pathTemplate: true #(6)!
metrics:
enabled: true #(7)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(8)!
tags: #(9)!
key1: value1
key2: value2
tracing:
enabled: true #(10)!
attributes: #(11)!
key1: value1
key2: value2
- Максимальное время запроса: может включать разрешение
DNS, подключение, запись тела запроса, обработку сервером и чтение тела ответа. Если вызов требует перенаправления или повторных попыток, все они должны завершиться в течение одного периода (по умолчанию не указано, необязательно) - Включает логирование модуля (по умолчанию:
false) - Маска, которая используется для скрытия указанных заголовков и параметров запроса или ответа (по умолчанию:
***) - Список параметров запроса, которые следует скрывать (по умолчанию:
[]) - Список заголовков запроса или ответа, которые следует скрывать (по умолчанию:
[ "authorization", "cookie", "set-cookie" ]) - Использовать ли шаблон пути запроса при логировании; если не указано, наследуется значение клиента (по умолчанию не указано, необязательно)
- Включает метрики модуля (по умолчанию:
true) - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO) - Настройка тегов для метрик (по умолчанию:
{}) - Включает трассировку модуля (по умолчанию:
true) - Настройка атрибутов для трассировки (по умолчанию:
{})
Запрос¶
Раздел описывает преобразования HTTP-запроса у декларативного HTTP-клиента.
Предлагается использовать специальные аннотации для указания параметров запроса.
Преобразование параметров в строку¶
StringParameterConverter<T> преобразует значение параметра в строку перед тем, как Kora подставит его в путь, параметр запроса,
заголовок или куки. Интерфейс состоит из одного метода:
Преобразователь ищется как обычный компонент графа по точному типу параметра. Если параметр имеет тип Map<String, T>,
то преобразователь ищется для типа значения T; если используется Map<String, List<T>>, он применяется к каждому элементу списка.
Из коробки доступны преобразователи для Boolean, Short, Integer, Long, Double, Float, UUID, BigDecimal, BigInteger,
Duration, OffsetTime, OffsetDateTime, LocalTime, LocalDate, LocalDateTime, ZonedDateTime и Instant.
Типы даты и времени записываются в ISO-формате. Для собственных типов нужно предоставить компонент StringParameterConverter<T>:
После этого тип можно использовать в параметрах клиента:
Параметр пути¶
@Path — обозначает значение части пути запроса, сам параметр указывается в {кавычках} в пути
и имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
Параметр запроса¶
@Query — значение параметра запроса, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
Поддерживаются одиночные значения, List<T>, Set<T>, Collection<T>, а также Map<String, T> и Map<String, List<T>>.
Для значений, которые не являются строками, используется доступный StringParameterConverter<T>.
Можно отправлять параметры запроса в формате ключ и значение, для этого предполагается использовать тип Map,
где ключом является имя параметра и обязательно имеет тип String.
Если значение Map является списком, каждый элемент списка будет отправлен как отдельное значение того же параметра.
Если элемент списка равен null, параметр будет отправлен без значения.
Заголовок¶
@Header — значение заголовка запроса, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
Поддерживаются одиночные значения, List<T>, Set<T>, Collection<T>, Map<String, T> и готовый объект HttpHeaders.
Можно отправлять заголовки в формате ключ и значение, для этого предполагается использовать тип HttpHeaders либо Map,
где ключом является имя заголовка и обязательно имеет тип String.
Для значений, которые не являются строками, используется доступный StringParameterConverter<T>:
Тело запроса¶
Для указания тела запроса требуется использовать аргумент метода без специальных аннотации,
по умолчанию поддерживаются такие типы как byte[], ByteBuffer или String.
JSON¶
Чтобы указать, что тело является JSON и для него требуется автоматически создать и внедрить JsonWriter,
используется тег-аннотация @Json:
Требуется подключить модуль JSON.
Текстовая форма¶
Можно использовать FormUrlEncoded как тип аргумента тела форма данных.
Пример вызова метода с такой формой будет выглядеть так:
Бинарная форма¶
Можно использовать FormMultipart как тип аргумента тела бинарная форма.
Пример вызова метода с такой формой будет выглядеть так:
Самописное¶
Если тело требуется записывать отличным от стандартных механизмов способом,
то можно использовать специальный интерфейс HttpClientRequestMapper для реализации собственной логики:
@HttpClient
public interface SomeClient {
record UserBody(String id) {}
final class UserRequestMapper implements HttpClientRequestMapper<UserBody> {
@Override
public HttpBodyOutput apply(Context ctx, UserBody value) {
return HttpBody.plaintext(value.id());
}
}
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
void hello(@Mapping(UserRequestMapper.class) UserBody body);
}
@HttpClient
interface SomeClient {
data class UserBody(val id: String)
class UserRequestMapper : HttpClientRequestMapper<UserBody> {
override fun apply(ctx: Context, value: UserBody): HttpBodyOutput {
return HttpBody.plaintext(value.id)
}
}
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun hello(@Mapping(UserRequestMapper::class) body: UserBody)
}
Пример: Protobuf сериализация
@HttpClient
public interface ProtobufClient {
final class ProtobufRequestMapper implements HttpClientRequestMapper<MyMessage> {
@Override
public HttpBodyOutput apply(Context ctx, MyMessage value) {
byte[] protobufBytes = value.toByteArray();
return HttpBody.of(protobufBytes, "application/x-protobuf");
}
}
@HttpRoute(method = HttpMethod.POST, path = "/message")
void sendMessage(@Mapping(ProtobufRequestMapper.class) MyMessage message);
}
@HttpClient
interface ProtobufClient {
class ProtobufRequestMapper : HttpClientRequestMapper<MyMessage> {
override fun apply(ctx: Context, value: MyMessage): HttpBodyOutput {
val protobufBytes = value.toByteArray()
return HttpBody.of(protobufBytes, "application/x-protobuf")
}
}
@HttpRoute(method = HttpMethod.POST, path = "/message")
fun sendMessage(@Mapping(ProtobufRequestMapper::class) message: MyMessage)
}
Куки¶
@Cookie — значение Cookie, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
Поддерживаются одиночные значения, List<T>, Set<T>, Collection<T>, Map<String, T> и готовый объект Cookie.
Куки добавляются в заголовок Cookie; для коллекций каждое значение превращается в отдельное значение куки с тем же именем.
Обязательные параметры¶
По умолчанию все аргументы объявленные в методе являются обязательными (NotNull).
По умолчанию все аргументы объявленные в методе которые не используют Kotlin Nullability синтаксис считаются обязательными (NotNull).
Необязательные параметры¶
Если аргумент метода является необязательным, то есть может отсутствовать то,
можно использовать аннотацию @Nullable:
@HttpClient
public interface SomeClient {
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
void hello(@Nullable @Query("queryValue") String queryValue); //(1)!
}
- Подойдет любая аннотация
@Nullable, такие какjavax.annotation.Nullable/jakarta.annotation.Nullable/org.jetbrains.annotations.Nullable/ и т.д.
Предполагается использовать Kotlin Nullability синтаксис и помечать такой параметр как Nullable:
Ответ¶
Раздел описывает преобразование HTTP-ответа от декларативного HTTP-клиента.
Тело ответа¶
По умолчанию можно использовать стандартные типы возвращаемых значений тела ответа, такие как void, byte[], ByteBuffer либо String.
JSON¶
Если предполагается читать тело как JSON, то требуется использовать аннотацию @Json над методом.
Требуется подключить модуль JSON.
Сущность ответа¶
Если предполагается читать тело и получить также заголовки и статус код ответа,
то предполагается использовать HttpResponseEntity, это обертка над телом ответа.
Ниже показан пример, аналогичный примеру JSON, вместе с оберткой HttpResponseEntity:
Самописное¶
Если требуется чтение ответа отличным способом, то можно использовать специальный интерфейс HttpClientResponseMapper:
@HttpClient
public interface SomeClient {
record MyResponse(String name) { }
final class ResponseMapper implements HttpClientResponseMapper<MyResponse> {
@Override
public MyResponse apply(HttpClientResponse response) throws IOException, HttpClientDecoderException {
try (var is = response.body().asInputStream()) {
final byte[] bytes = is.readAllBytes();
var body = new String(bytes, StandardCharsets.UTF_8);
return new MyResponse(body);
}
}
}
@Mapping(ResponseMapper.class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
MyResponse hello();
}
@HttpClient
interface SomeClient {
data class MyResponse(val name: String)
class ResponseMapper : HttpClientResponseMapper<MyResponse> {
@Throws(IOException::class, HttpClientDecoderException::class)
override fun apply(response: HttpClientResponse): MyResponse {
response.body().asInputStream().use {
val bytes: ByteArray = it.readAllBytes()
val body = String(bytes, StandardCharsets.UTF_8)
return MyResponse(body)
}
}
}
@Mapping(ResponseMapper::class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
fun hello(): MyResponse
}
Пример: Обработка ошибок в маппере
@HttpClient
public interface ApiClient {
record ApiResponse(String status, Object data) {}
final class SafeResponseMapper implements HttpClientResponseMapper<ApiResponse> {
private final JsonReader<ApiResponse> jsonReader;
public SafeResponseMapper(JsonReader<ApiResponse> jsonReader) {
this.jsonReader = jsonReader;
}
@Override
public ApiResponse apply(HttpClientResponse response) throws IOException {
int statusCode = response.statusCode();
byte[] body = response.body();
if (statusCode >= 400) {
// Обработка ошибки: логирование или выброс исключения
throw new HttpClientResponseException(statusCode, body, response.headers());
}
if (body == null || body.length == 0) {
return null;
}
return jsonReader.read(body);
}
}
@HttpRoute(method = HttpMethod.GET, path = "/api/data")
@Mapping(SafeResponseMapper.class)
ApiResponse getData();
}
@HttpClient
interface ApiClient {
data class ApiResponse(val status: String, val data: Any?)
class SafeResponseMapper(
private val jsonReader: JsonReader<ApiResponse>
) : HttpClientResponseMapper<ApiResponse> {
@Throws(IOException::class)
override fun apply(response: HttpClientResponse): ApiResponse {
val statusCode = response.statusCode()
val body = response.body()
if (statusCode >= 400) {
// Обработка ошибки: логирование или выброс исключения
throw HttpClientResponseException(statusCode, body, response.headers())
}
if (body == null || body.isEmpty()) {
return null
}
return jsonReader.read(body)
}
}
@HttpRoute(method = HttpMethod.GET, path = "/api/data")
@Mapping(SafeResponseMapper::class)
fun getData(): ApiResponse
}
Ошибка ответа¶
По умолчанию, когда не указан ни тег преобразователя, ни сам преобразователь, преобразование применяется только для 2xx HTTP-кодов ответа.
Для всех остальных кодов будет выброшено исключение HttpClientResponseException, которое содержит HTTP-код ответа, тело ответа и заголовки ответа.
Исключения клиента¶
Все штатные исключения HTTP-клиента наследуются от HttpClientException, который является RuntimeException.
Это позволяет перехватывать как конкретный вид ошибки, так и все ошибки клиента одним общим типом:
try {
client.getUser("123");
} catch (HttpClientResponseException e) {
var code = e.getCode();
var headers = e.getHeaders();
var body = e.getBytes();
} catch (HttpClientException e) {
throw e;
}
Основные типы исключений:
HttpClientResponseException— ответ получен, но его код не был обработан как успешный. СодержитgetCode(),getHeaders()иgetBytes().HttpClientTimeoutException— истекло время ожидания запроса, соединения или чтения.HttpClientConnectionException— ошибка установления или поддержания соединения с удаленным узлом.HttpClientEncoderException— ошибка преобразования пользовательского значения в тело запроса.HttpClientDecoderException— ошибка преобразования тела ответа в пользовательский тип.HttpClientUnknownException— прочая ошибка транспортного клиента, которая не попала в более точную категорию.
HttpClientResponseException создается после чтения тела ответа в массив байт. Если тело не удалось прочитать полностью,
ошибка чтения добавляется как suppressed-исключение, а в getBytes() попадает то тело, которое удалось собрать.
Преобразование по коду¶
Если требуется особое преобразование в зависимости от HTTP-кода ответа, можно использовать аннотацию @ResponseCodeMapper для указания
соответствия HTTP-кода и преобразователя HttpClientResponseMapper.
Также можно использовать ResponseCodeMapper.DEFAULT как указание поведения по умолчанию для всех неперечисленных HTTP-кодов.
Если для кода указан параметр mapper, будет использован конкретный HttpClientResponseMapper.
Если указан параметр type, Kora подберет преобразователь ответа для этого типа и затем приведет результат к возвращаемому типу метода.
Это удобно для закрытых иерархий ответов, где разные HTTP-статусы соответствуют разным подтипам результата.
@HttpClient
public interface SomeClient {
record UserResponse(UserResponse.Payload payload, UserResponse.Error error) {
public record Error(int code, String message) {}
public record Payload(String message) {}
}
@ResponseCodeMapper(code = ResponseCodeMapper.DEFAULT, mapper = ResponseErrorMapper.class)
@ResponseCodeMapper(code = 200, mapper = ResponseSuccessMapper.class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
UserResponse hello();
}
@HttpClient
interface SomeClient {
data class UserResponse(val payload: Payload, val error: Error) {
data class Error(val code: Int, val message: String)
data class Payload(val message: String)
}
@ResponseCodeMapper(code = ResponseCodeMapper.DEFAULT, mapper = ResponseErrorMapper::class)
@ResponseCodeMapper(code = 200, mapper = ResponseSuccessMapper::class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
fun hello(): UserResponse
}
В примере выше для статуса кода 200 будет использовать ResponseSuccessMapper,
а для всех остальных статус кодов будет использован ResponseErrorMapper.
Пример с параметром type:
@HttpClient
public interface SomeClient {
@Json
sealed interface UserResponse permits Success, Error {}
@Json
record Success(String id) implements UserResponse {}
@Json
record Error(String message) implements UserResponse {}
@Json
@ResponseCodeMapper(code = 200, type = Success.class)
@ResponseCodeMapper(code = 404, type = Error.class)
@HttpRoute(method = HttpMethod.GET, path = "/users/{id}")
UserResponse get(@Path String id);
}
@HttpClient
interface SomeClient {
@Json
sealed interface UserResponse
@Json
data class Success(val id: String) : UserResponse
@Json
data class Error(val message: String) : UserResponse
@Json
@ResponseCodeMapper(code = 200, type = Success::class)
@ResponseCodeMapper(code = 404, type = Error::class)
@HttpRoute(method = HttpMethod.GET, path = "/users/{id}")
fun get(@Path id: String): UserResponse
}
Сигнатуры¶
Доступные сигнатуры для методов декларативного HTTP-клиента из коробки:
Под T подразумевается тип возвращаемого значения, либо Void.
T myMethod()CompletionStage<T> myMethod()CompletionStageMono<T> myMethod()Project Reactor (надо подключить зависимость)
Под T подразумевается тип возвращаемого значения, либо Unit.
myMethod(): Tsuspend myMethod(): TKotlin Coroutine (надо подключить зависимость какimplementation)
Перехватчики¶
Можно создавать перехватчики для изменения поведения либо создания дополнительного поведения используя интерфейс HttpClientInterceptor.
Перехватчики можно подключить на определенные методы либо весь @HttpClient класс целиком с помощью аннотации @InterceptWith.
Перехватчик на метод:
@HttpClient
public interface SomeClient {
final class MethodInterceptor implements HttpClientInterceptor {
private final Component1 component1;
private MethodInterceptor(Component1 component1) {
this.component1 = component1;
}
@Override
public CompletionStage<HttpClientResponse> processRequest(Context ctx, InterceptChain chain, HttpClientRequest request) throws Exception {
component1.doSomething();
return chain.process(ctx, request);
}
}
@InterceptWith(MethodInterceptor.class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
void hello();
}
@HttpClient
interface SomeClient {
class MethodInterceptor(val component1: Component1) : HttpClientInterceptor {
@Throws(Exception::class)
override fun processRequest(
ctx: Context,
chain: HttpClientInterceptor.InterceptChain,
request: HttpClientRequest
): CompletionStage<HttpClientResponse> {
component1.doSomething()
return chain.process(ctx, request)
}
}
@InterceptWith(MethodInterceptor::class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
fun hello()
}
Перехватчик на весь класс:
Порядок выполнения перехватчиков:
Перехватчики выполняются в порядке объявления (слева направо). Каждый перехватчик может:
- Модифицировать запрос перед отправкой
- Вызвать следующий перехватчик в цепочке (chain.process())
- Модифицировать ответ после получения
- Выбросить исключение и прервать цепочку
Запрос → Interceptor1 → Interceptor2 → Interceptor3 → HTTP сервер
Ответ ← Interceptor1 ← Interceptor2 ← Interceptor3 ← HTTP сервер
Перехватчик на клиент¶
Для применения перехватчика ко всем клиентам можно зарегистрировать его как компонент без @InterceptWith:
@Component
public class GlobalInterceptor implements HttpClientInterceptor {
@Override
public CompletionStage<HttpClientResponse> processRequest(Context ctx, InterceptChain chain, HttpClientRequest request) throws Exception {
// Применяется ко всем HTTP клиентам
return chain.process(ctx, request);
}
}
@Component
class GlobalInterceptor : HttpClientInterceptor {
@Throws(Exception::class)
override fun processRequest(
ctx: Context,
chain: HttpClientInterceptor.InterceptChain,
request: HttpClientRequest
): CompletionStage<HttpClientResponse> {
// Применяется ко всем HTTP клиентам
return chain.process(ctx, request)
}
}
Базовый URL¶
RootUriInterceptor — готовый перехватчик, который добавляет базовый URL к относительным запросам.
Если запрос уже содержит схему (http:// или https://), перехватчик оставляет его без изменений.
Если запрос относительный, RootUriInterceptor добавляет к нему корневой адрес и гарантирует один разделитель / между корнем и путем.
После регистрации перехватчика его можно подключить к клиенту:
Для декларативных клиентов обычно удобнее задавать базовый URL через конфигурацию DeclarativeHttpClientConfig.url.
RootUriInterceptor полезен для императивного HttpClient или для случаев, когда общий корневой адрес нужно добавить как отдельное сквозное поведение.
@HttpClient
public interface SomeClient {
final class MethodInterceptor implements HttpClientInterceptor {
private final Component1 component1;
private MethodInterceptor(Component1 component1) {
this.component1 = component1;
}
@Override
public CompletionStage<HttpClientResponse> processRequest(Context ctx, InterceptChain chain, HttpClientRequest request) throws Exception {
component1.doSomething();
return chain.process(ctx, request);
}
}
@InterceptWith(MethodInterceptor.class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
void hello();
}
@HttpClient
interface SomeClient {
class MethodInterceptor(val component1: Component1) : HttpClientInterceptor {
@Throws(Exception::class)
override fun processRequest(
ctx: Context,
chain: HttpClientInterceptor.InterceptChain,
request: HttpClientRequest
): CompletionStage<HttpClientResponse> {
component1.doSomething()
return chain.process(ctx, request)
}
}
@InterceptWith(MethodInterceptor::class)
@HttpRoute(method = HttpMethod.GET, path = "/hello/world")
fun hello()
}
Если перехватчик нужен для всех методов клиента, @InterceptWith можно поставить на интерфейс:
Если перехватчики указаны и на клиенте, и на методе, для конкретного вызова будут применены оба набора перехватчиков.
Авторизация¶
Kora предоставляет готовые перехватчики, которые можно использовать для авторизации с помощью Basic/ApiKey/Bearer/OAuth
Basic¶
Требуется сконфигурировать перехватчик и конфигурацию для авторизации Basic:
@Module
public interface BasicAuthModule {
@ConfigSource("openapiAuth.basicAuth")
public interface BasicAuthConfig {
String username();
String password();
}
default BasicAuthHttpClientInterceptor basicAuther(BasicAuthConfig config) {
return new BasicAuthHttpClientInterceptor(config.username(), config.password());
}
}
@Module
interface BasicAuthModule {
@ConfigSource("openapiAuth.basicAuth")
interface BasicAuthConfig {
fun username(): String
fun password(): String
}
fun basicAuther(config: BasicAuthConfig): BasicAuthHttpClientInterceptor {
return BasicAuthHttpClientInterceptor(config.username(), config.password())
}
}
Также в конструктор можно предоставить собственную реализацию HttpClientTokenProvider если правила получения секретов другие.
Затем подключить перехватчик для всего HTTP-клиента либо определенных методов.
ApiKey¶
Требуется сконфигурировать перехватчик и конфигурацию для авторизации ApiKey:
@Module
public interface ApiKeyAuthModule {
@ConfigSource("openapiAuth.apiKeyAuth")
interface ApiKeyAuthConfig {
String apiKey();
}
default ApiKeyHttpClientInterceptor apiKeyAuther(ApiKeyAuthConfig config) {
return new ApiKeyHttpClientInterceptor(ApiKeyLocation.HEADER, "X-API-KEY", config.apiKey());
}
}
Затем подключить перехватчик для всего HTTP-клиента либо определенных методов.
Bearer¶
Требуется сконфигурировать перехватчик для авторизации Bearer:
Потребуется самостоятельно реализовать предоставление Bearer токена с помощью собственной реализации HttpClientTokenProvider,
либо использовать конструктор который принимает статический Bearer Token.
public interface HttpClientTokenProvider {
CompletionStage<String> getToken(HttpClientRequest request);
}
Затем подключить перехватчик для всего HTTP-клиента либо определенных методов.
OAuth¶
Авторизация с помощью OAuth аналогична Bearer,
требуется самостоятельно реализовать HttpClientTokenProvider и подложить его в контейнер зависимостей.
Предоставление токена¶
HttpClientTokenProvider — интерфейс для предоставления токенов авторизации динамически.
Используется когда токен нужно обновлять или получать из внешнего источника (например, OAuth2 token endpoint).
Пример реализации:
@Component
public class MyTokenProvider implements HttpClientTokenProvider {
private final OAuthClient oauthClient;
private volatile String cachedToken;
private volatile long tokenExpiry;
public MyTokenProvider(OAuthClient oauthClient) {
this.oauthClient = oauthClient;
}
@Override
public CompletionStage<String> getToken(HttpClientRequest request) {
if (cachedToken != null && System.currentTimeMillis() < tokenExpiry) {
return CompletableFuture.completedFuture(cachedToken);
}
// Получить новый токен
return oauthClient.refreshToken()
.thenApply(response -> {
this.cachedToken = response.accessToken();
this.tokenExpiry = System.currentTimeMillis() + response.expiresIn() * 1000;
return this.cachedToken;
});
}
}
@Component
class MyTokenProvider(
private val oauthClient: OAuthClient
) : HttpClientTokenProvider {
private var cachedToken: String? = null
private var tokenExpiry: Long = 0
override fun getToken(request: HttpClientRequest): CompletionStage<String> {
if (cachedToken != null && System.currentTimeMillis() < tokenExpiry) {
return CompletableFuture.completedFuture(cachedToken)
}
// Получить новый токен
return oauthClient.refreshToken()
.thenApply { response ->
cachedToken = response.accessToken()
tokenExpiry = System.currentTimeMillis() + response.expiresIn() * 1000
cachedToken!!
}
}
}
Использование с BearerAuthHttpClientInterceptor:
Обработка исключений¶
При выполнении HTTP запросов могут возникать различные исключения. Все исключения наследуются от базового HttpClientException.
Иерархия исключений:
HttpClientException
├── HttpClientTimeoutException
├── HttpClientConnectionException
├── HttpClientResponseException
├── HttpClientEncoderException
├── HttpClientDecoderException
└── HttpClientUnknownException
Пример обработки:
@Component
class SomeService {
private final SomeClient client;
public SomeService(SomeClient client) {
this.client = client;
}
public void call() {
try {
client.hello();
} catch (HttpClientTimeoutException e) {
// Таймаут: логирование, повторная попытка
} catch (HttpClientConnectionException e) {
// Ошибка соединения: проверка доступности сервиса
} catch (HttpClientResponseException e) {
// Ошибка ответа: statusCode, body, headers
int statusCode = e.getStatusCode();
byte[] body = e.getBody();
} catch (HttpClientEncoderException e) {
// Ошибка сериализации: проверка данных
} catch (HttpClientDecoderException e) {
// Ошибка десериализации: логирование
} catch (HttpClientUnknownException e) {
// Неизвестная ошибка: e.getCause()
}
}
}
@Component
class SomeService(
private val client: SomeClient
) {
fun call() {
try {
client.hello()
} catch (e: HttpClientTimeoutException) {
// Таймаут: логирование, повторная попытка
} catch (e: HttpClientConnectionException) {
// Ошибка соединения: проверка доступности сервиса
} catch (e: HttpClientResponseException) {
// Ошибка ответа: statusCode, body, headers
val statusCode = e.statusCode
val body = e.body
} catch (e: HttpClientEncoderException) {
// Ошибка сериализации: проверка данных
} catch (e: HttpClientDecoderException) {
// Ошибка десериализации: логирование
} catch (e: HttpClientUnknownException) {
// Неизвестная ошибка: e.cause
}
}
}
Время ожидания¶
Выбрасывается когда запрос превышает установленное время ожидания (requestTimeout или connectTimeout).
Причины:
- Сервер не отвечает в течение requestTimeout
- Превышено время установления соединения (connectTimeout)
- Сетевые задержки
Рекомендации: - Настройте адекватные таймауты в конфигурации - Реализуйте retry-логику для временных сбоев - Используйте circuit breaker для защиты от cascading failures
Ошибка соединения¶
Выбрасывается когда не удалось установить соединение с сервером.
Причины: - DNS не разрешается - Сервер недоступен (port closed, firewall) - Соединение отклонено - SSL/TLS handshake failed
Рекомендации: - Проверьте доступность сервиса (health check) - Используйте fallback на резервный сервис - Настройте retry с exponential backoff
Ошибка клиента и сервера¶
Выбрасывается когда сервер вернул HTTP статус код ошибки (4xx или 5xx) и не указан собственный маппер через @ResponseCodeMapper.
Доступные данные:
- statusCode — HTTP статус код (400, 404, 500, etc.)
- body — тело ответа (может содержать детали ошибки)
- headers — заголовки ответа
Рекомендации:
- Используйте @ResponseCodeMapper для кастомной обработки статусов
- Логируйте statusCode и body для отладки
- Различайте клиентские (4xx) и серверные (5xx) ошибки
Ошибка запроса¶
Выбрасывается когда произошла ошибка при сериализации тела запроса.
Причины: - Ошибка JSON/XML сериализации - Невалидные данные в объекте запроса - Отсутствие сериализатора для типа
Рекомендации:
- Валидируйте данные перед отправкой
- Проверьте наличие Json-аннотаций на классах
- Логируйте оригинальное исключение в cause
Ошибка ответа¶
Выбрасывается когда произошла ошибка при десериализации тела ответа.
Причины: - Невалидный JSON/XML в ответе сервера - Несоответствие схемы (сервер вернул неожиданные поля) - Отсутствие десериализатора для типа
Рекомендации:
- Проверьте совместимость версий API
- Логируйте тело ответа для отладки
- Используйте @ResponseCodeMapper для обработки ошибок формата
Ошибка неизвестная¶
Выбрасывается когда произошла неизвестная ошибка, не подпадающая под другие категории.
Доступные данные:
- cause — оригинальное исключение
Рекомендации:
- Всегда логируйте cause для диагностики
- Проверьте логи HTTP клиента на уровне DEBUG/TRACE
- Сообщите о баге если исключение воспроизводится
Клиент императивный¶
Базовый клиент представляет собой интерфейс HttpClient и доступен для внедрения:
public interface HttpClient {
CompletionStage<HttpClientResponse> execute(HttpClientRequest request); //(1)!
HttpClient with(HttpClientInterceptor interceptor); //(2)!
}
- Метод исполнения запроса
- Метод позволяющий добавлять различные перехватчики в ручном режиме
Для построения запросов вручную можно использовать HttpClientRequestBuilder:
Построитель запроса¶
HttpClientRequestBuilder позволяет строить HTTP запросы вручную.
Построитель URI¶
UriQueryBuilder помогает строить URI с параметрами запроса.
Тело запроса¶
HttpBodyInput — интерфейс который описывает тело HTTP запроса как поток данных (Flow.Publisher
Методы:
| Метод | Возвращает | Описание |
|---|---|---|
asInputStream() |
InputStream |
Представляет тело как InputStream для чтения |
asBufferStage() |
CompletionStage<ByteBuffer> |
Асинхронно читает всё тело в ByteBuffer |
asArrayStage() |
CompletionStage<byte[]> |
Асинхронно читает всё тело в byte[] |
Ответ клиента¶
HttpClientResponse — интерфейс который представляет HTTP ответ от сервера.
Методы:
| Метод | Возвращает | Описание |
|---|---|---|
statusCode() |
int |
HTTP статус код (200, 404, 500, etc.) |
body() |
byte[] |
Тело ответа как массив байтов |
headers() |
HttpHeaders |
Заголовки ответа |
cookies() |
Cookies |
Cookies из ответа |
Заголовки¶
HttpHeaders предоставляет доступ к заголовкам запроса и ответа в императивном клиенте.
Чтение заголовков:
HttpClientRequest request = HttpClientRequest.of("GET", "http://localhost:8090/api/data")
.build();
httpClient.execute(request).thenAccept(response -> {
HttpHeaders headers = response.headers();
String contentType = headers.getFirst("Content-Type");
List<String> allValues = headers.get("X-Custom-Header");
boolean hasHeader = headers.contains("Authorization");
});
val request = HttpClientRequest.of("GET", "http://localhost:8090/api/data").build()
httpClient.execute(request).thenAccept { response ->
val headers = response.headers
val contentType = headers.getFirst("Content-Type")
val allValues = headers.get("X-Custom-Header")
val hasHeader = headers.contains("Authorization")
}
Добавление заголовков:
MutableHttpHeaders headers = new MutableHttpHeaders();
headers.add("Authorization", "Bearer token123");
headers.add("X-Custom-Header", "value");
headers.set("Content-Type", "application/json");
HttpClientRequest request = HttpClientRequest.of("POST", "http://localhost:8090/api/data")
.headers(headers)
.body(HttpBody.plaintext("body"))
.build();
httpClient.execute(request);
val headers = MutableHttpHeaders()
headers.add("Authorization", "Bearer token123")
headers.add("X-Custom-Header", "value")
headers.set("Content-Type", "application/json")
val request = HttpClientRequest.of("POST", "http://localhost:8090/api/data")
.headers(headers)
.body(HttpBody.plaintext("body"))
.build()
httpClient.execute(request)
Cookies¶
Cookies предоставляет доступ к cookies запроса и ответа в императивном клиенте.
Чтение cookies:
HttpClientRequest request = HttpClientRequest.of("GET", "http://localhost:8090/api/profile")
.build();
httpClient.execute(request).thenAccept(response -> {
Cookies cookies = response.cookies();
Cookie sessionCookie = cookies.get("SESSIONID");
if (sessionCookie != null) {
String value = sessionCookie.value();
String domain = sessionCookie.domain();
String path = sessionCookie.path();
}
});
val request = HttpClientRequest.of("GET", "http://localhost:8090/api/profile").build()
httpClient.execute(request).thenAccept { response ->
val cookies = response.cookies
val sessionCookie = cookies.get("SESSIONID")
if (sessionCookie != null) {
val value = sessionCookie.value()
val domain = sessionCookie.domain()
val path = sessionCookie.path()
}
}
Телеметрия¶
HTTP Client использует контракт телеметрии для логирования, метрик и трассировки запросов.
Конфигурация телеметрии (секция telemetry { logging / metrics / tracing }) описана в разделе Конфигурация.
Точки расширения находятся в ru.tinkoff.kora.http.client.common.telemetry.
Для каждого HTTP-запроса создаётся HttpClientTelemetry.HttpClientTelemetryContext, который закрывается по завершении запроса.
Запрос описывается через параметры обработчика телеметрии, включая метод, URL, статус ответа и длительность.
Фабрика по умолчанию DefaultHttpClientTelemetryFactory объединяет три фабрики:
- HttpClientLoggerFactory строит HttpClientLogger для логирования начала/конца запроса;
- HttpClientMetricsFactory строит HttpClientMetrics для записи метрик запросов;
- HttpClientTracerFactory строит HttpClientTracer для распределённой трассировки.
Метрики и трассировка описаны в разделе Справочник метрик.