Ktor Client для Kotlin: полное руководство с примерами
Ktor Client — это мощная, легковесная и мультиплатформенная библиотека для выполнения HTTP-запросов на языке Kotlin. Разработанная компанией JetBrains, она является частью экосистемы Ktor и предоставляет асинхронный, корутино-ориентированный API для взаимодействия с веб-серверами. В отличие от многих других HTTP-клиентов, Ktor Client изначально спроектирован для работы на нескольких платформах: JVM, Android, iOS, macOS, Linux, Windows и JavaScript (через Kotlin/JS). Это делает его идеальным выбором для разработки кроссплатформенных приложений на Kotlin Multiplatform (KMP).
Зачем нужен Ktor Client? В современной разработке практически ни одно приложение не обходится без взаимодействия с удалёнными API. Будь то загрузка данных из REST-сервиса, отправка форм, загрузка файлов или работа с WebSockets — HTTP-клиент является фундаментальным инструментом. Ktor Client предлагает не просто базовые возможности GET и POST запросов, а целую экосистему плагинов (или "движков"), которые позволяют настраивать сериализацию (JSON, XML, Protobuf), сжатие, логирование, аутентификацию, повторные попытки и многое другое. Его главное преимущество — полная асинхронность на основе корутин Kotlin, что обеспечивает высокую производительность и лаконичный код без callback-ов.
Библиотека отличается модульной архитектурой. Вы подключаете только те компоненты, которые реально нужны вашему проекту: базовый клиент, конкретный движок (например, OkHttp для Android/JVM или Darwin для iOS), сериализатор и необходимые плагины. Это минимизирует размер итогового приложения и избегает конфликтов зависимостей. Ktor Client активно развивается, имеет отличную документацию и поддерживается сообществом Kotlin.
Установка
Ktor Client распространяется через систему сборки Gradle (или Maven). Поскольку это не Python-пакет, команда pip install не используется. Ниже приведены точные инструкции для добавления зависимостей в ваш build.gradle.kts файл.
Важно: Версии библиотек должны быть совместимы. Рекомендуется использовать последнюю стабильную версию из официального репозитория Maven Central. На момент написания статьи актуальная версия — 2.3.12.
// build.gradle.kts (Gradle Kotlin DSL)
// 1. Добавьте репозиторий (обычно уже есть)
repositories {
mavenCentral()
}
// 2. Добавьте зависимости в блок dependencies
dependencies {
// Базовый клиент (обязательно)
implementation("io.ktor:ktor-client-core:2.3.12")
// Движок (Engine) - выберите один из вариантов:
// Для JVM (использует Java HttpClient или OkHttp)
implementation("io.ktor:ktor-client-okhttp:2.3.12") // Рекомендуется для Android/JVM
// implementation("io.ktor:ktor-client-java:2.3.12") // Альтернатива для JVM
// Для iOS/macOS (нативный Darwin)
// implementation("io.ktor:ktor-client-darwin:2.3.12")
// Для JavaScript
// implementation("io.ktor:ktor-client-js:2.3.12")
// Для Windows/Linux (Curl)
// implementation("io.ktor:ktor-client-curl:2.3.12")
// Плагины (опционально, но очень полезно):
// Сериализация JSON (Gson или Kotlinx Serialization)
implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
implementation("io.ktor:ktor-serialization-gson:2.3.12")
// или implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
// Логирование
implementation("io.ktor:ktor-client-logging:2.3.12")
// Для работы с WebSockets
// implementation("io.ktor:ktor-client-websockets:2.3.12")
}
Если вы используете Maven, зависимости будут выглядеть аналогично. После добавления зависимостей, синхронизируйте проект (Sync Project with Gradle Files).
Быстрый старт: минимальный рабочий пример
Давайте напишем простейшую программу, которая выполняет GET-запрос к публичному API и выводит ответ в консоль. Этот пример демонстрирует базовую структуру работы с Ktor Client.
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 1. Создаем HTTP-клиент
val client = HttpClient()
// 2. Выполняем GET-запрос
val response: HttpResponse = client.get("https://api.github.com")
// 3. Читаем тело ответа как строку
val body: String = response.bodyAsText()
// 4. Выводим результат
println("Статус: ${response.status}")
println("Тело ответа (первые 200 символов): ${body.take(200)}")
// 5. Обязательно закрываем клиент для освобождения ресурсов
client.close()
}
Объяснение:
HttpClient()— создает экземпляр клиента. Если не указан движок, Ktor попытается автоматически выбрать подходящий для вашей платформы.client.get(...)— выполняет асинхронный GET-запрос. ВозвращаетHttpResponse.response.bodyAsText()— преобразует тело ответа в строку.runBlocking— используется только в main-функции для запуска корутины. В реальных приложениях (Android, Spring) вы будете использовать другие корутин-билдеры (launch,async).client.close()— критически важно закрывать клиент, когда он больше не нужен, чтобы избежать утечки ресурсов (соединений, потоков).
Основные методы, функции и классы
Ktor Client предоставляет богатый API. Рассмотрим 8 ключевых элементов, которые вы будете использовать чаще всего.
1. HttpClient (класс)
Сигнатура: class HttpClient(engine: HttpClientEngine? = null, block: HttpClientConfig<*>.() -> Unit = {})
Что делает: Главный класс для выполнения HTTP-запросов. Конфигурируется с помощью лямбда-выражения, где можно установить движок, таймауты, плагины и другие настройки.
// Создание клиента с конфигурацией
val client = HttpClient(OkHttp) { // Указываем движок OkHttp
install(ContentNegotiation) {
json(Gson { // Настраиваем сериализацию JSON
prettyPrint = true
setPrettyPrinting()
})
}
install(Logging) {
level = LogLevel.BODY // Логируем все запросы и ответы
}
defaultRequest {
url("https://api.example.com/") // Базовый URL для всех запросов
}
}
2. client.get() (метод)
Сигнатура: suspend fun HttpClient.get(urlString: String, block: HttpRequestBuilder.() -> Unit = {}): HttpResponse
Что делает: Выполняет HTTP GET-запрос. Позволяет добавлять заголовки, параметры запроса и другие настройки через builder.
val response: HttpResponse = client.get("https://api.github.com/users/octocat") {
header("Accept", "application/vnd.github.v3+json")
parameter("per_page", 10) // Добавляем query-параметр
}
println(response.bodyAsText())
3. client.post() (метод)
Сигнатура: suspend fun HttpClient.post(urlString: String, block: HttpRequestBuilder.() -> Unit = {}): HttpResponse
Что делает: Выполняет HTTP POST-запрос. Используется для отправки данных на сервер (например, создание ресурса).
val response = client.post("https://jsonplaceholder.typicode.com/posts") {
contentType(ContentType.Application.Json)
setBody("""{"title": "foo", "body": "bar", "userId": 1}""")
}
println("Создан пост с ID: ${response.bodyAsText()}")
4. client.put() (метод)
Сигнатура: suspend fun HttpClient.put(urlString: String, block: HttpRequestBuilder.() -> Unit = {}): HttpResponse
Что делает: Выполняет HTTP PUT-запрос. Обычно используется для полного обновления существующего ресурса.
val response = client.put("https://jsonplaceholder.typicode.com/posts/1") {
contentType(ContentType.Application.Json)
setBody("""{"id": 1, "title": "updated title", "body": "updated body", "userId": 1}""")
}
println(response.status)
5. client.delete() (метод)
Сигнатура: suspend fun HttpClient.delete(urlString: String, block: HttpRequestBuilder.() -> Unit = {}): HttpResponse
Что делает: Выполняет HTTP DELETE-запрос. Используется для удаления ресурса на сервере.
val response = client.delete("https://jsonplaceholder.typicode.com/posts/1")
println("Ресурс удален. Статус: ${response.status}")
6. HttpResponse (класс)
Сигнатура: class HttpResponse(val status: HttpStatusCode, val request: HttpRequest, val headers: Headers, val body: ByteReadChannel, ...)
Что делает: Представляет ответ сервера. Содержит статус, заголовки и тело ответа. Предоставляет методы для чтения тела в различных форматах.
val response: HttpResponse = client.get("https://httpbin.org/get")
println("Статус: ${response.status}") // HttpStatusCode
println("Заголовок Content-Type: ${response.headers["Content-Type"]}")
println("Длина тела: ${response.contentLength()}")
// Чтение тела разными способами
val textBody: String = response.bodyAsText()
val byteArray: ByteArray = response.body()
// Если настроен ContentNegotiation, можно десериализовать в объект:
// val myObject: MyDataClass = response.body()
7. HttpRequestBuilder (класс)
Сигнатура: class HttpRequestBuilder { var url: URLBuilder; var method: HttpMethod; var headers: HeadersBuilder; var body: OutgoingContent; ... }
Что делает: Строитель для конфигурации HTTP-запроса. Используется в лямбда-блоке методов get, post и т.д. Позволяет устанавливать URL, заголовки, тело, параметры и таймауты.
client.get("https://api.example.com/search") {
// Настройка URL
url {
parameters["q"] = "kotlin"
parameters["page"] = "1"
}
// Заголовки
header("Authorization", "Bearer my_token")
header("User-Agent", "KtorClient/2.0")
// Таймауты
timeout {
requestTimeoutMillis = 5000
connectTimeoutMillis = 3000
}
}
8. ContentNegotiation (плагин)
Сигнатура: fun ContentNegotiationConfig.json(block: JsonBuilder.() -> Unit = {}) (и другие форматы)
Что делает: Плагин для автоматической сериализации и десериализации тела запроса/ответа. Поддерживает JSON (Gson, Kotlinx Serialization), XML и другие форматы. Позволяет работать с объектами напрямую, без ручного преобразования в строку.
// Настройка
val client = HttpClient {
install(ContentNegotiation) {
json(Gson {
ignoreUnknownFields = true
serializeNulls()
})
}
}
// Data class
data class Post(val userId: Int, val id: Int, val title: String, val body: String)
// Использование в запросе
suspend fun createPost(client: HttpClient): Post {
val newPost = Post(userId = 1, id = 0, title = "My Post", body = "Content")
val response: HttpResponse = client.post("https://jsonplaceholder.typicode.com/posts") {
contentType(ContentType.Application.Json)
setBody(newPost) // Автоматическая сериализация в JSON
}
return response.body() // Автоматическая десериализация из JSON в Post
}
Реальный практический пример: Консольный клиент для GitHub API
Давайте создадим полноценное консольное приложение, которое взаимодействует с GitHub API. Оно будет получать информацию о пользователе, список его репозиториев и выводить статистику. Этот пример объединяет все основные концепции: конфигурацию, GET/POST запросы, сериализацию, обработку ошибок и работу с корутинами.
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.gson.*
import kotlinx.coroutines.*
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
// Data классы для десериализации ответов GitHub API
data class GitHubUser(
val login: String,
val id: Long,
val name: String?,
val public_repos: Int,
val followers: Int,
val following: Int,
val avatar_url: String?,
val html_url: String
)
data class GitHubRepo(
val id: Long,
val name: String,
val full_name: String,
val description: String?,
val html_url: String,
val stargazers_count: Int,
val forks_count: