Cliente HTTP em Kotlin: fazendo requisições corretamente — nível avançado
No desenvolvimento de aplicações modernas em Kotlin, o trabalho com requisições HTTP vai muito além de uma simples chamada a URL.readText(). No nível avançado, é necessário considerar gerenciamento de conexões, timeouts, tentativas repetidas, logging, assincronicidade e tratamento correto de erros. Neste artigo, vamos abordar como configurar um cliente HTTP em Kotlin para ambiente de produção, utilizando duas bibliotecas populares: OkHttp e Ktor Client. O material é voltado para desenvolvedores que já conhecem os fundamentos do Kotlin e HTTP.
1. Escolha da biblioteca: OkHttp vs Ktor Client
Antes de escrever código, é importante entender a diferença entre as abordagens. OkHttp é uma biblioteca madura e testada pelo tempo, da Square, que funciona muito bem em conjunto com o Retrofit. Ktor Client é uma biblioteca nativa multiplataforma da JetBrains, totalmente integrada com as corrotinas do Kotlin. Para projetos server-side com Spring Boot, geralmente se escolhe OkHttp, enquanto para aplicações Ktor ou projetos multiplataforma, a escolha é o Ktor Client.
Abordaremos ambas as opções para que você possa escolher a mais adequada para sua stack.
2. Configuração avançada do OkHttp
A configuração básica do OkHttp é trivial, mas para produção é necessária uma configuração mais refinada. Vejamos a criação de um cliente Singleton com 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 Interceptadores personalizados (Interceptors)
Os interceptadores são um mecanismo poderoso para modificar requisições e respostas. Abaixo está um exemplo de um interceptador de logging e um interceptador para tentativas repetidas.
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("Tentativa #${retryCount + 1} falhou: ${e.message}") } retryCount++ if (retryCount < maxRetries) Thread.sleep(1000L * retryCount) } return response ?: throw IOException("Máximo de tentativas excedido") }}2.2 Requisições assíncronas com corrotinas
O OkHttp suporta requisições síncronas e assíncronas (baseadas em callbacks). Para integração com as corrotinas do Kotlin, usamos suspendCancellableCoroutine.
suspend fun makeRequest(url: String): String { return suspendCancellableCoroutine { continuation -> val request = Request.Builder().url(url).build() val call = HttpClientProvider.getClient().newC