HTTP клиент
Модуль предоставляет тонкий слой абстракции для создания HTTP-клиентов с помощью аннотаций в декларативном стиле, либо использование клиентов в императивном стиле.
AsyncHttpClient¶
Реализация HTTP клиента основанная на библиотеке Async HTTP Client.
Подключение¶
Конфигурация¶
Пример полной конфигурации, описанной в классе 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)!
}
}
- Следовать ли по перенаправлениям в HTTP
- Максимальное время на установление соединения
- Максимальное время на чтение ответа
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
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)!
- Следовать ли по перенаправлениям в HTTP
- Максимальное время на установление соединения
- Максимальное время на чтение ответа
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
Можно также настроить Netty транспорт.
OkHttp¶
Реализация HTTP клиента основанная на библиотеке OkHttp. Учитывайте что реализация написана на Kotlin и использует соответствующие зависимости.
Подключение¶
Конфигурация¶
Пример полной конфигурации, описанной в классе OkHttpClientConfig
и HttpClientConfig
(указаны примеры значений или значения по умолчанию):
httpClient {
ok {
followRedirects = true //(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)!
}
}
- Следовать ли по перенаправлениям в HTTP
- Максимальная используемая версия HTTP протокола (доступные значения:
HTTP_1_1
/HTTP_2
/HTTP_3
) - Максимальное время на установление соединения
- Максимальное время на чтение ответа
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
httpClient:
ok:
followRedirects: true #(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)!
- Следовать ли по перенаправлениям в HTTP
- Максимальная используемая версия HTTP протокола (доступные значения:
HTTP_1_1
/HTTP_2
/HTTP_3
) - Максимальное время на установление соединения
- Максимальное время на чтение ответа
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
Конфигуратор¶
Пример настройки построителя OkHttp клиента, OkHttpConfigurer
должен быть доступен как компонент:
Нативный клиент¶
Реализация HTTP клиента на основании нативного клиента поставляемого в JDK.
Подключение¶
Конфигурация¶
Пример полной конфигурации, описанной в классе JdkHttpClientConfig
и HttpClientConfig
(указаны примеры значений или значения по умолчанию):
httpClient {
jdk {
threads = 2 //(1)!
httpVersion = "HTTP_1_1" //(2)!
}
connectTimeout = "5s" //(3)!
useEnvProxy = false //(4)!
proxy {
host = "localhost" //(5)!
port = 8090 //(6)!
user = "user" //(7)!
password = "password" //(8)!
nonProxyHosts = [ "host1", "host2" ] //(9)!
}
}
- Количество потоков для HTTP клиента, по умолчанию равен кол-во ядер процессора умноженных на 2
- Какую версию HTTP протокола использовать (доступные значения:
HTTP_1_1
/HTTP_2
) - Максимальное время на установление соединения
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
httpClient:
jdk:
threads: 2 #(1)!
httpVersion: "HTTP_1_1" #(2)!
connectTimeout: "2s" #(3)!
useEnvProxy: false #(4)!
proxy:
host: "localhost" #(5)!
port: 8090 #(6)!
user: "user" #(7)!
password: "password" #(8)!
nonProxyHosts: [ "host1", "host2" ] #(9)!
- Количество потоков для HTTP клиента, по умолчанию равен кол-во ядер процессора умноженных на 2
- Какую версию HTTP протокола использовать (доступные значения:
HTTP_1_1
/HTTP_2
) - Максимальное время на установление соединения
- Использовать ли переменные окружения для настройки прокси
- Адрес прокси (по умолчанию отсутвует)
- Порт прокси (по умолчанию отсутвует)
- Пользователь для прокси (по умолчанию отсутвует)
- Пароль для прокси (по умолчанию отсутвует)
- Хосты которые следует исключить из проксирования (по умолчанию отсутвует)
Клиент декларативный¶
Предлагается использовать специальные аннотации для создания декларативного клиента:
@HttpClient
— указывает что интерфейс является декларативным HTTP клиентом@HttpRoute
— указывает тип HTTP запроса и путь запроса
Конфигурация клиента¶
Конфигурация конкретной реализации @HttpClient
по умолчанию для поиска конфигурации использует следующий путь httpClient.{имя класса в нижнем регистре}
,
либо указывается в параметре configPath
в аннотации:
Пример конфигурации в случае пути path.to.config
описанной в классе DeclarativeHttpClientConfig
:
path {
to {
config {
url = "https://localhost:8090" //(1)!
requestTimeout = "10s" //(2)!
telemetry {
logging {
enabled = false //(3)!
}
metrics {
enabled = true //(4)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(5)!
}
tracing {
enabled = true //(6)!
}
}
}
}
}
- URL сервиса куда будут отправляться запросы
- Максимальное время запроса
- Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
path:
to:
config:
url: "https://localhost:8090" #(1)!
requestTimeout: "10s" #(2)!
telemetry:
logging:
enabled: false #(3)!
metrics:
enabled: true #(4)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(5)!
telemetry:
enabled: true #(6)!
- URL сервиса куда будут отправляться запросы
- Максимальное время запроса
- Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
Конфигурация метода¶
На примере выше рассмотренного HTTP клиента, можно настроить отдельно часть параметров для определенного метода, путь к конфигурации
определяется путем к клиенту и именем метода, в примере выше конфигурация path.to.config
и метода hello
финальный путь будет path.to.config.getHello
path {
to {
config {
hello {
requestTimeout = "10s" //(1)!
telemetry {
logging {
enabled = false //(2)!
}
metrics {
enabled = true //(3)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(4)!
}
tracing {
enabled = true //(5)!
}
}
}
}
}
}
- Максимальное время запроса метода
- Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
path:
to:
config:
hello:
requestTimeout: "10s" #(1)!
telemetry:
logging:
enabled: false #(2)!
metrics:
enabled: true #(3)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(4)!
telemetry:
enabled: true #(5)!
- Максимальное время запроса метода
- Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
Запрос¶
Секция описывает преобразования HTTP запроса у декларативного HTTP клиента. Предлагается использовать специальные аннотации для указания параметров запроса.
Параметр пути¶
@Path
— обозначает значение части пути запроса, сам параметр указывается в {кавычках}
в пути
и имя параметра указывается в value
либо по умолчанию равно имени аргумента метода.
Параметр запроса¶
@Query
— значение параметра запроса, имя параметра указывается в value
либо по умолчанию равно имени аргумента метода.
Можно отправлять параметры запроса в формате ключ и значение, для этого предполагается использовать тип Map
,
где ключом является имя параметра и обязано иметь тип String
, а значение параметра может быть любым типом и будет обработано через String.valueOf()
:
Заголовок¶
@Header
— значение заголовка запроса, имя параметра указывается в value
либо по умолчанию равно имени аргумента метода.
Можно отправлять параметры запроса в формате ключ и значение, для этого предполагается использовать HttpHeaders
тип либо тип Map
,
где ключом является имя параметра и обязано иметь тип String
, а значение параметра может быть любым типом и будет обработано через String.valueOf()
:
Тело запроса¶
Для указания тела запроса требуется использовать аргумент метода без специальных аннотации,
по умолчанию поддерживаются такие типы как byte[]
, ByteBuffer
или String
.
Json¶
Для указания, что тело является Json и ему требуется автоматически создать такого писателя и внедрить его,
требуется использовать аннотацию @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)
}
Куки¶
@Cookie
— значение Cookie, имя параметра указывается в value
либо по умолчанию равно имени аргумента метода.
Обязательные параметры¶
По умолчанию все аргументы объявленные в методе являются обязательными (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
}
Ошибка ответа¶
По умолчанию преобразование будет применяться только для 2хх
HTTP статусов кодов,
для всех остальных будет выбрасываться исключение HttpClientResponseException
, которое содержит HTTP статус код, тело ответа и заголовки ответа.
Преобразование по коду¶
Если требуются специфичные преобразование в зависимости от HTTP статус кода ответа, можно использовать аннотацию @ResponseCodeMapper
для указания
соответствия HTTP статус кода и преобразователя HttpClientResponseMapper
.
Также можно использовать ResponseCodeMapper.DEFAULT
как указание поведения по умолчанию для всех не перечисленных статус кодов.
@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
.
Сигнатуры¶
Доступные сигнатуры для методов декларативного HTTP клиента из коробки:
Под T
подразумевается тип возвращаемого значения, либо Void
.
T myMethod()
CompletionStage<T> myMethod()
CompletionStageMono<T> myMethod()
Project Reactor (надо подключить зависимость)
Под T
подразумевается тип возвращаемого значения, либо Unit
.
myMethod(): T
suspend myMethod(): T
Kotlin Coroutine (надо подключить зависимость какimplementation
)
Перехватчики¶
Можно создавать перехватчики для изменения поведения либо создания дополнительного поведения используя класс HttpClientInterceptor
.
Перехватчики можно подключить на определенные методы либо весь @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()
}
Авторизация¶
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
и подложить его в контейнер зависимостей.
Клиент императивный¶
Базовый клиент представляет собой интерфейс HttpClient
и доступен для внедрения:
public interface HttpClient {
CompletionStage<HttpClientResponse> execute(HttpClientRequest request); //(1)!
HttpClient with(HttpClientInterceptor interceptor); //(2)!
}
- Метод исполнения запроса
- Метод позволяющий добавлять различные перехватчики в ручном режиме
Для построения запросов вручную можно использовать HttpClientRequestBuilder
: