Cliente HTTP en Kotlin: haciendo solicitudes correctamente — nivel avanzado
Al desarrollar aplicaciones modernas en Kotlin, el trabajo con solicitudes HTTP va mucho más allá de una simple llamada a URL.readText(). A nivel avanzado, es necesario considerar la gestión de conexiones, tiempos de espera, reintentos, registro de actividad, asincronía y un manejo correcto de errores. En este artículo, analizaremos cómo configurar un cliente HTTP en Kotlin para un entorno de producción, utilizando dos bibliotecas populares: OkHttp y Ktor Client. El material está dirigido a desarrolladores que ya conocen los fundamentos de Kotlin y HTTP.
1. Elección de la biblioteca: OkHttp vs Ktor Client
Antes de escribir código, es importante entender la diferencia entre los enfoques. OkHttp es una biblioteca madura y probada de Square, que funciona muy bien junto con Retrofit. Ktor Client es una biblioteca nativa multiplataforma de JetBrains, completamente integrada con las corrutinas de Kotlin. Para proyectos de servidor con Spring Boot, a menudo se elige OkHttp, mientras que para aplicaciones Ktor o proyectos multiplataforma se prefiere Ktor Client.
Analizaremos ambas opciones para que puedas elegir la más adecuada para tu stack tecnológico.
2. Configuración avanzada de OkHttp
La configuración básica de OkHttp es trivial, pero para producción se requiere una configuración más detallada. Veamos cómo crear un cliente Singleton con parámetros personalizados.
import okhttp3.*import java.util.concurrent.TimeUnit
object HttpClientProvider {
private val client: OkHttpClient by lazy { OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .retryOnConnectionFailure(true) .addInterceptor(LoggingInterceptor()) .addInterceptor(RetryInterceptor(maxRetries = 3)) .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES)) .build() }
fun getClient(): OkHttpClient = client}2.1 Interceptores personalizados (Interceptors)
Los interceptores son un mecanismo potente para modificar solicitudes y respuestas. A continuación, se muestra un ejemplo de un interceptor de registro y un interceptor para reintentos.
class LoggingInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() println("--> ${request.method} ${request.url}") val startTime = System.nanoTime() val response = chain.proceed(request) val duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) println("<-- ${response.code} ${response.message} (${duration}ms)") return response }}
class RetryInterceptor(private val maxRetries: Int) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { var retryCount = 0 var response: Response? = null while (retryCount < maxRetries) { try { response = chain.proceed(chain.request()) if (response.isSuccessful) return response } catch (e: Exception) { println("Reintento #${retryCount + 1} falló: ${e.message}") } retryCount++ if (retryCount < maxRetries) Thread.sleep(1000L * retryCount) } return response ?: throw IOException("Máximo de reintentos excedido") }}2.2 Solicitudes asíncronas con corrutinas
OkHttp admite solicitudes síncronas y asíncronas (basadas en callbacks). Para la integración con corrutinas de Kotlin, utilizamos suspendCancellableCoroutine.
suspend fun makeRequest(url: String): String { return suspendCancellableCoroutine { continuation -> val request = Request.Builder().url(url).build() val call = HttpClientProvider.getClient().newC