HTTP клиент на Kotlin: делаем запросы правильно
Современная разработка под Android и бэкенд-сервисы на Kotlin редко обходится без взаимодействия с удаленными API. Создание надежного HTTP клиента — базовая задача, от правильного решения которой зависит стабильность всего приложения. В этой статье мы разберем, как делать HTTP запросы на Kotlin, используя две самые популярные библиотеки: Ktor Client и OkHttp. Вы узнаете, как отправлять GET и POST запросы, обрабатывать JSON, управлять ошибками и писать чистый, поддерживаемый код.
Почему Ktor Client — выбор современного Kotlin-разработчика
Ktor Client — это асинхронный HTTP клиент, созданный специально для Kotlin командой JetBrains. Он полностью поддерживает корутины (coroutines), что делает код лаконичным и безопасным. В отличие от OkHttp, Ktor изначально спроектирован с учетом мультиплатформенности: один и тот же код работает на Android, iOS, JVM, JS и Native.
Установка зависимостей
Для работы с Ktor Client добавьте следующие зависимости в файл build.gradle.kts:
dependencies {
// Базовый клиент
implementation("io.ktor:ktor-client-core:2.3.7")
// Движок для JVM (OkHttp или CIO)
implementation("io.ktor:ktor-client-okhttp:2.3.7")
// Для сериализации JSON
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
// Kotlinx Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
Простой GET запрос
Создадим HTTP клиент и выполним GET запрос к тестовому API (JSONPlaceholder):
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.runBlocking
suspend fun fetchUser(userId: Int): String {
val client = HttpClient {
// Базовая конфигурация
engine {
// Настройки OkHttp движка
}
}
val response: HttpResponse = client.get("https://jsonplaceholder.typicode.com/users/$userId")
return response.bodyAsText()
}
fun main() = runBlocking {
val userData = fetchUser(1)
println(userData)
}
Обратите внимание: fetchUser — suspend функция. Это позволяет вызывать её из корутины без блокировки потока.
POST запрос с JSON телом
Для отправки данных в формате JSON подключим плагин ContentNegotiation и определим data-класс:
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class Post(val userId: Int, val id: Int? = null, val title: String, val body: String)
suspend fun createPost(post: Post): Post {
val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
prettyPrint = true
})
}
}
val response: Post = client.post("https://jsonplaceholder.typicode.com/posts") {
contentType(ContentType.Application.Json)
setBody(post)
}.body()
client.close()
return response
}
fun main() = runBlocking {
val newPost = Post(userId = 1, title = "Ktor Client", body = "Работает отлично!")
val createdPost = createPost(newPost)
println(createdPost)
}
Ключевой момент: плагин ContentNegotiation автоматически сериализует объект в JSON и десериализует ответ обратно в data-класс.
OkHttp — проверенный стандарт для Android
OkHttp остается де-факто стандартом для сетевых запросов в Android-разработке. Он быстрый, надежный и поддерживает перехватчики (interceptors), кэширование и WebSocket. Рассмотрим, как использовать OkHttp в Kotlin-проекте.
Базовая настройка и GET запрос
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
fun fetchUsers(): String {
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/users")
.get()
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw RuntimeException("Ошибка: ${response.code}")
return response.body?.string() ?: "Пустой ответ"
}
}
fun main() {
val users = fetchUsers()
println(users)
}
Обратите внимание на .use — это гарантирует закрытие ресурсов (Response body) даже при возникновении исключения.
POST запрос с JSON на OkHttp
Для отправки JSON потребуется создать тело запроса вручную или использовать библиотеку сериализации (например, Gson или kotlinx.serialization):
import com.google.gson.Gson
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
data class User(val name: String, val email: String)
fun createUser(user: User): String {
val json = Gson().toJson(user)
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = json.toRequestBody(mediaType)
val request = Request.Builder()
.url("https://jsonplaceholder.typicode.com/users")
.post(requestBody)
.build()
client.newCall(request).execute().use { response ->
return response.body?.string() ?: "Ошибка"
}
}
fun main() {
val newUser = User(name = "Иван Петров", email = "ivan@example.com")
val result = createUser(newUser)
println(result)
}
OkHttp не требует корутин для базовых запросов, но для асинхронной работы используйте enqueue с callback или оборачивайте вызовы в корутины с помощью suspendCancellableCoroutine.
Обработка ошибок и лучшие практики
Независимо от выбранной библиотеки, важно правильно обрабатывать ошибки и структурировать код. Вот несколько рекомендаций:
- Используйте sealed class для состояний ответа. Создайте обертку, которая возвращает успешный результат или ошибку, вместо выбрасывания исключений.
- Настройте таймауты. Всегда устанавливайте