HTTP клиент на Kotlin: делаем запросы правильно: от А до Я
Современная разработка на Kotlin немыслима без сетевого взаимодействия. Будь то Android-приложение, серверное решение на Ktor или десктопное приложение — HTTP-запросы остаются основным способом обмена данными. В этой статье мы разберем, как правильно организовать HTTP-клиент на Kotlin, рассмотрим популярные библиотеки и лучшие практики.
Почему Kotlin идеален для HTTP-запросов?
Kotlin предлагает современный синтаксис, нулевую безопасность и полную совместимость с Java-экосистемой. Благодаря корутинам и потоковым API, работа с сетью становится чистой и асинхронной. Основные преимущества:
- Корутины — легковесные потоки для асинхронных операций без блокировок
- Type-safe builders — удобное конструирование HTTP-запросов
- Sealed classes — элегантная обработка состояний ответа
- Extension functions — расширение функциональности библиотек
Выбор HTTP-клиента для Kotlin
На сегодняшний день три библиотеки доминируют в экосистеме Kotlin: OkHttp, Ktor Client и Retrofit. Рассмотрим каждую с примерами кода.
1. OkHttp — проверенный временем выбор
OkHttp от Square — наиболее популярный HTTP-клиент для JVM. Он эффективен, поддерживает HTTP/2, автоматическое сжатие и кэширование.
// build.gradle.kts
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// Пример GET-запроса с OkHttp
import okhttp3.OkHttpClient
import okhttp3.Request
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class OkHttpExample {
private val client = OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.build()
suspend fun fetchData(url: String): String = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url(url)
.header("Accept", "application/json")
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw RuntimeException("Ошибка: ${response.code}")
}
response.body?.string() ?: throw RuntimeException("Пустой ответ")
}
}
}
2. Ktor Client — нативный выбор для Kotlin
Ktor Client — часть экосистемы JetBrains, полностью написан на Kotlin с поддержкой мультиплатформенности. Идеален для Ktor-серверов и Android/iOS проектов.
// build.gradle.kts
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
@Serializable
data class Post(val userId: Int, val id: Int, val title: String, val body: String)
class KtorExample {
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
engine {
requestTimeout = 30_000
}
}
suspend fun getPost(id: Int): Post {
val response: HttpResponse = client.get("https://jsonplaceholder.typicode.com/posts/$id")
return response.body()
}
suspend fun createPost(post: Post): Post {
val response: HttpResponse = client.post("https://jsonplaceholder.typicode.com/posts") {
setBody(post)
contentType(io.ktor.http.ContentType.Application.Json)
}
return response.body()
}
}
3. Retrofit — стандарт для Android-разработки
Retrofit — высокоуровневая обертка над OkHttp, используемая в большинстве Android-проектов. Позволяет декларативно описывать API.
// build.gradle.kts
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
interface ApiService {
@GET("posts/{id}")
suspend fun getPost(@Path("id") id: Int): Post
@POST("posts")
suspend fun createPost(@Body post: Post): Post
@GET("posts")
suspend fun getPostsByUser(@Query("userId") userId: Int): List<Post>
}
object RetrofitClient {
private val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val client = OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.build()
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
Продвинутые техники работы с HTTP-клиентом
Обработка ошибок и повторные попытки
Надежное приложение должно корректно обрабатывать сетевые ошибки. Используйте sealed class для моделирования состояний ответа.
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val message: String, val code: Int? = null) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}
suspend fun <T> safeApiCall(call: suspend () -> T): NetworkResult<T> {
return try {
NetworkResult.Success(call())
} catch (e: IOException) {
NetworkResult.Error("Нет соединения с интернетом: ${e.message}")
} catch (e: HttpException) {
NetworkResult.Error("Ошибка сервера: ${e.code()}", e.code())
} catch (e: Exception) {
NetworkResult.Error("Неизвестная ошибка: ${e.message}")
}
}
// Использование
fun main() = runBlocking {
val result = safeApiCall {
RetrofitClient.apiService.getPost(1)
}
when (result) {
is