Usare proxy in Kotlin: guida pratica con Ktor Client e OkHttp

Guida code-first per usare proxy residenziali in Kotlin con Ktor 3 e OkHttp: configurazione, autenticazione, SOCKS5, coroutine concorrenti e hardening di produzione.

Using Proxies in Kotlin: A Code-First Guide with Ktor and OkHttp
In questo articolo

Lavorare con proxy HTTP in Kotlin è un'esigenza comune sia su backend JVM sia su Android. Che tu stia facendo Kotlin web scraping, monitoraggio prezzi o SERP tracking, devi gestire autenticazione proxy, rotazione IP, geo-targeting e resilienza ai blocchi. Questa guida copre l'uso di proxy in Kotlin con Ktor Client 3 (engine CIO/OkHttp) e OkHttp nativo, con esempi runnable e pattern di produzione.

Perché usare proxy residenziali in Kotlin

Molti target — app mobile, social network, marketplace — filtrano attivamente le richieste provenienti da ASN datacenter come AWS, Google Cloud o DigitalOcean. Uno studio di DataDome mostra che oltre il 50% del traffico automatizzato verso siti e-commerce viene bloccato prima di raggiungere l'applicazione. I proxy residenziali instradano le richieste attraverso IP assegnati a veri ISP, riducendo drasticamente la probabilità di ricevere un 403 o un challenge CAPTCHA.

Il problema tecnico è duplice:

  • Il header Proxy-Authorization non è gestito in modo uniforme da tutti i client HTTP: in Ktor CIO va aggiunto manualmente, in OkHttp esiste un Authenticator dedicato.
  • La rotazione e il geo-targeting richiedono di codificare parametri nello username, un pattern non immediatamente ovvio.

Setup del progetto: Ktor 3 e OkHttp

Aggiungi le dipendenze in build.gradle.kts. Per un progetto backend JVM usiamo Ktor 3 con engine CIO; per Android o per chi preferisce OkHttp, includiamo anche l'engine OkHttp di Ktor e OkHttp nativo.

dependencies {
    // Ktor Client 3.x core + engine CIO
    implementation("io.ktor:ktor-client-core:3.0.3")
    implementation("io.ktor:ktor-client-cio:3.0.3")
    // Engine OkHttp per Ktor (utile su Android)
    implementation("io.ktor:ktor-client-okhttp:3.0.3")
    // OkHttp nativo per uso diretto
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    // Coroutines per fan-out concorrente
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

Baseline OkHttp con proxy esplicito

Partiamo da un esempio minimo con OkHttpClient.Builder().proxy(java.net.Proxy(...)). Questo è il punto di riferimento prima di aggiungere autenticazione e rotazione.

import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit

fun main() {
    val client = OkHttpClient.Builder()
        .proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()

    val request = Request.Builder()
        .url("https://httpbin.org/ip")
        .build()

    client.newCall(request).execute().use { response ->
        println(response.code)
        println(response.body?.string())
    }
}

Senza credenziali riceverai un 407 Proxy Authentication Required. Vediamo come risolverlo.

Routing tramite gate.proxyhat.com:8080 con geo-targeting e sessioni sticky

ProxyHat usa il gateway gate.proxyhat.com:8080 per HTTP e :1080 per SOCKS5. Le credenziali includono flag di geo-targeting e sessione direttamente nello username, separati da trattini:

  • user-country-DE-city-berlin — IP residenziale a Berlino, Germania.
  • user-country-US-session-abc123 — IP statunitense con sessione sticky abc123 (lo stesso IP per richieste successive).
  • user-country-IT-session-ord7 — IP italiano, sessione persistente.

Ktor Client: Proxy-Authorization in defaultRequest

In Ktor con engine CIO, l'autenticazione proxy non è gestita automaticamente dal motore: devi aggiungere l'header Proxy-Authorization: Basic ... tramite defaultRequest. Questo è un dettaglio engine-specific che causa molti mal di testa.

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import java.util.Base64

suspend fun main() {
    val proxyUser = "user-country-DE-city-berlin"
    val proxyPass = "pass"
    val authHeader = "Basic " +
        Base64.getEncoder().encodeToString("$proxyUser:$proxyPass".toByteArray())

    val client = HttpClient(CIO) {
        engine {
            proxy = ProxyBuilder.http(
                Url("http://gate.proxyhat.com:8080")
            )
        }
        defaultRequest {
            header("Proxy-Authorization", authHeader)
        }
    }

    try {
        val resp: HttpResponse = client.get("https://httpbin.org/ip")
        println(resp.status)
        println(resp.bodyAsText())
    } finally {
        client.close()
    }
}

Per cambiare IP ad ogni richiesta basta variare lo username (es. aggiungendo un UUID come sessione). Per mantenere la stessa IP, riusa lo stesso identificativo di sessione.

OkHttp: Authenticator per sfide 407

OkHttp offre un percorso più elegante tramite Authenticator, che intercetta automaticamente le risposte 407 e aggiunge Proxy-Authorization.

import okhttp3.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit

fun buildOkClient(user: String, pass: String): OkHttpClient {
    val credential = Credentials.basic(user, pass)
    return OkHttpClient.Builder()
        .proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
        .proxyAuthenticator { _: Route, response: Response ->
            response.request.newBuilder()
                .header("Proxy-Authorization", credential)
                .build()
        }
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .build()
}

fun main() {
    val client = buildOkClient("user-country-US-session-s1", "pass")
    val req = Request.Builder().url("https://httpbin.org/ip").build()
    client.newCall(req).execute().use { r ->
        println(r.code)
        println(r.body?.string())
    }
}

SOCKS5 sulla porta 1080

Per il protocollo SOCKS5, ProxyHat espone la porta 1080. Su JVM puoi impostare le proprietà di sistema socksProxyHost, socksProxyPort, java.net.socks.username e java.net.socks.password prima di creare il client. Questo funziona sia con Ktor CIO sia con OkHttp, poiché entrambi rispettano le proprietà globali della JVM.

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*

suspend fun main() {
    System.setProperty("socksProxyHost", "gate.proxyhat.com")
    System.setProperty("socksProxyPort", "1080")
    System.setProperty("java.net.socks.username", "user-country-FR-session-sock9")
    System.setProperty("java.net.socks.password", "pass")

    val client = HttpClient(CIO)
    try {
        val resp = client.get("https://httpbin.org/ip")
        println(resp.status)
        println(resp.bodyAsText())
    } finally {
        client.close()
    }
}

Nota: le proprietà di sistema sono globali. In un'app multi-tenant preferisci un'istanza java.net.Proxy con un java.net.Authenticator custom, o l'engine OkHttp di Ktor che supporta proxy SOCKS per-client.

Perché i residenziali servono per app e social: fan-out con coroutine

Target come Instagram, TikTok, Ticketmaster o sneaker shop bloccano rapidamente IP datacenter. I proxy residenziali di ProxyHat instradano tramite ISP reali, abbassando il tasso di blocco. Per sfruttare la concorrenza di Kotlin senza saturare il gateway, usiamo async/awaitAll con un Semaphore per il rate control.

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import java.util.Base64

suspend fun fetchOne(client: HttpClient, authHeader: String, url: String): String {
    return client.get(url) {
        header("Proxy-Authorization", authHeader)
        header(HttpHeaders.UserAgent, "Mozilla/5.0")
    }.bodyAsText()
}

suspend fun main() = coroutineScope {
    val urls = listOf(
        "https://httpbin.org/ip?r=1",
        "https://httpbin.org/ip?r=2",
        "https://httpbin.org/ip?r=3",
        "https://httpbin.org/ip?r=4",
        "https://httpbin.org/ip?r=5",
        "https://httpbin.org/ip?r=6",
        "https://httpbin.org/ip?r=7",
        "https://httpbin.org/ip?r=8"
    )
    val authHeader = "Basic " +
        Base64.getEncoder().encodeToString("user-country-US:pass".toByteArray())

    val client = HttpClient(CIO) {
        engine {
            proxy = ProxyBuilder.http(Url("http://gate.proxyhat.com:8080"))
        }
    }

    val semaphore = kotlinx.coroutines.sync.Semaphore(4) // max 4 richieste concorrenti

    try {
        val results = urls.map { url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    runCatching { fetchOne(client, authHeader, url) }
                        .getOrElse { "ERR: ${it.message}" }
                }
            }
        }.awaitAll()

        results.forEachIndexed { i, r -> println("#$i: $r") }
    } finally {
        client.close()
    }
}

Con Semaphore(4) limiti la concorrenza a 4 richieste in-flight, evitando di saturare il gateway e riducendo il rischio di rate limiting lato target. Per carichi maggiori, scala il numero di permessi e usa sessioni sticky distinte per shard logico (es. una sessione per categoria di prodotto).

Hardening di produzione

Retry, timeout e connection pooling in OkHttp

In produzione i timeout e i retry sono essenziali. OkHttp supporta retryOnConnectionFailure, Dispatcher per limitare richieste concorrenti e ConnectionPool per riutilizzare connessioni.

import okhttp3.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit

fun buildProductionClient(user: String, pass: String): OkHttpClient {
    val credential = Credentials.basic(user, pass)
    return OkHttpClient.Builder()
        .proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
        .proxyAuthenticator { _, response ->
            response.request.newBuilder()
                .header("Proxy-Authorization", credential)
                .build()
        }
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .callTimeout(60, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(ConnectionPool(50, 5, TimeUnit.MINUTES))
        .dispatcher(Dispatcher().apply {
            maxRequests = 64
            maxRequestsPerHost = 16
        })
        .build()
}

TLS e cipher

Per evitare fingerprinting TLS, usa il ConnectionSpec di OkHttp per forzare TLS 1.2/1.3 e cipher moderni. Su Android 7+ (API 24+) il NetworkSecurityConfig in res/xml/network_security_config.xml controlla trust anchor e cleartext; dichiaralo nel AndroidManifest.xml con android:networkSecurityConfig.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">gate.proxyhat.com</domain>
    </domain-config>
</network-security-config>

Il cleartext su gate.proxyhat.com è necessario perché il tunnel HTTP CONNECT verso il proxy avviene in chiaro prima del TLS end-to-end verso il target.

Circuit breaker con coroutine

Per evitare di bombardare un target già in errore, implementa un circuit breaker semplice con MutableStateFlow o usa una libreria come Resilience4j:

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class SimpleCircuitBreaker(
    private val failureThreshold: Int = 5,
    private val resetTimeoutMs: Long = 30_000L
) {
    private val state = MutableStateFlow(State.CLOSED)
    private var failures = 0
    private var lastFailureTime = 0L
    private val mutex = Mutex()

    enum class State { CLOSED, OPEN, HALF_OPEN }

    suspend fun <T> execute(block: suspend () -> T): T {
        if (state.value == State.OPEN) {
            if (System.currentTimeMillis() - lastFailureTime > resetTimeoutMs) {
                state.value = State.HALF_OPEN
            } else {
                throw IllegalStateException("Circuit open")
            }
        }
        return try {
            val result = block()
            mutex.withLock { failures = 0; state.value = State.CLOSED }
            result
        } catch (e: Exception) {
            mutex.withLock {
                failures++
                lastFailureTime = System.currentTimeMillis()
                if (failures >= failureThreshold) state.value = State.OPEN
            }
            throw e
        }
    }
}

Considerazioni etiche e legali

Il web scraping è legale quando si raccolgono dati pubblici senza violare i termini di servizio o le normative sulla privacy. Negli Stati Uniti il Computer Fraud and Abuse Act (CFAA) disciplina l'accesso non autorizzato a sistemi informatici; nella sentenza hiQ Labs v. LinkedIn (2022) la corte ha confermato che lo scraping di dati pubblici non viola il CFAA. Nell'UE il GDPR si applica ai dati personali: se raccogli email, nomi o altri dati identificativi, devi avere una base giuridica.

Linee guida pratiche:

  • Rispetta robots.txt e i Retry-After header.
  • Limita la concorrenza per non impattare la disponibilità del target (usa il Semaphore mostrato sopra).
  • Preferisci API ufficiali quando disponibili (es. API di Twitter/X, Serpstack, Google Custom Search).
  • Non raccogliere dati personali senza base giuridica GDPR.

Per approfondire i casi d'uso, consulta le pagine web scraping e SERP tracking di ProxyHat, oppure la lista delle locazioni proxy disponibili.

Configurazione ProxyHat e SDK

ProxyHat espone un gateway unificato: HTTP su gate.proxyhat.com:8080, SOCKS5 su :1080. Lo username codifica geo-targeting e sessione. Il pattern è identico a quello mostrato negli esempi: cambia solo lo username per ruotare IP o cambiare paese. La documentazione ufficiale è su docs.proxyhat.com, e i piani sono visibili sulla pagina pricing.

Il ProxyHat SDK (quando disponibile per il tuo linguaggio) incapsula lo stesso pattern: costruttore di client con credenziali proxy, rotazione automatica delle sessioni e fallback tra pool residenziali. In Kotlin puoi replicarlo facilmente con una factory function che genera OkHttpClient o HttpClient parametrizzati per paese/sessione.

Punti chiave

  • Ktor CIO richiede l'header Proxy-Authorization manuale in defaultRequest; OkHttp usa proxyAuthenticator.
  • Codifica geo-targeting e sessioni sticky nello username: user-country-DE-city-berlin-session-abc123.
  • Usa Semaphore con async/awaitAll per rate control nel fan-out concorrente.
  • Imposta timeout, connection pool e dispatcher per evitare saturazione del gateway.
  • I proxy residenziali riducono i blocchi su target che filtrano ASN datacenter.
  • Rispetta robots.txt, CFAA e GDPR; preferisci API ufficiali quando possibili.

FAQ

Cos'è l'uso di proxy in Kotlin?

L'uso di proxy in Kotlin consiste nell'instradare le richieste HTTP o SOCKS di un client (Ktor Client, OkHttp, java.net.HttpURLConnection) attraverso un server proxy che nasconde l'IP del chiamante e, opzionalmente, applica geo-targeting e rotazione. In Kotlin si implementa configurando l'engine del client (CIO, OkHttp o Java) con un ProxyBuilder o un java.net.Proxy, aggiungendo credenziali via header Proxy-Authorization o Authenticator.

Perché l'uso di proxy in Kotlin è importante per gli sviluppatori?

I proxy sono essenziali per il web scraping su larga scala, il testing geo-localizzato e l'accesso a contenuti con restrizioni geografiche. Senza proxy, gli IP datacenter vengono bloccati rapidamente da WAF e anti-bot. In Kotlin, la combinazione di coroutine e client HTTP moderni permette di gestire centinaia di richieste concorrenti con rotazione IP automatica, massimizzando il successo e minimizzando i blocchi.

Quale tipo di proxy funziona meglio in Kotlin?

I proxy residenziali sono i più efficaci per target che filtrano ASN datacenter (social, e-commerce, app mobile). I proxy datacenter sono più veloci e economici, adatti a target meno protetti o a compiti di testing interno. I proxy mobile offrono la massima affidabilità su app con anti-bot aggressivo. In Kotlin, la scelta non cambia il codice: cambia solo lo username passato al gateway ProxyHat.

Come evitare i blocchi quando si usano proxy in Kotlin?

Usa proxy residenziali con rotazione per-request, imposta header realistici (User-Agent, Accept-Language), limita la concorrenza con un Semaphore (es. 4–8 richieste concorrenti), rispetta Retry-After e implementa retry con backoff esponenziale. Mantieni sessioni sticky quando il target richiede cookie o token di sessione coerenti. Aggiungi un circuit breaker per sospendere le richieste quando il tasso di errore supera una soglia.

Si può usare SOCKS5 con Ktor Client in Kotlin?

Sì, tramite le proprietà di sistema JVM socksProxyHost, socksProxyPort, java.net.socks.username e java.net.socks.password, oppure configurando un java.net.Proxy(Proxy.Type.SOCKS, ...) con un java.net.Authenticator custom. ProxyHat espone SOCKS5 sulla porta 1080 di gate.proxyhat.com.

Domande frequenti

Cos'è l'uso di proxy in Kotlin?

L'uso di proxy in Kotlin consiste nell'instradare le richieste HTTP o SOCKS di un client (Ktor Client, OkHttp, java.net.HttpURLConnection) attraverso un server proxy che nasconde l'IP del chiamante e, opzionalmente, applica geo-targeting e rotazione. In Kotlin si implementa configurando l'engine del client con un ProxyBuilder o un java.net.Proxy, aggiungendo credenziali via header Proxy-Authorization o Authenticator.

Perché l'uso di proxy in Kotlin è importante per gli sviluppatori?

I proxy sono essenziali per il web scraping su larga scala, il testing geo-localizzato e l'accesso a contenuti con restrizioni geografiche. Senza proxy, gli IP datacenter vengono bloccati rapidamente da WAF e anti-bot. In Kotlin, la combinazione di coroutine e client HTTP moderni permette di gestire centinaia di richieste concorrenti con rotazione IP automatica, massimizzando il successo e minimizzando i blocchi.

Quale tipo di proxy funziona meglio in Kotlin?

I proxy residenziali sono i più efficaci per target che filtrano ASN datacenter come social, e-commerce e app mobile. I proxy datacenter sono più veloci e economici, adatti a target meno protetti o a testing interno. I proxy mobile offrono la massima affidabilità su app con anti-bot aggressivo. In Kotlin la scelta non cambia il codice: cambia solo lo username passato al gateway ProxyHat.

Come evitare i blocchi quando si usano proxy in Kotlin?

Usa proxy residenziali con rotazione per-request, imposta header realistici (User-Agent, Accept-Language), limita la concorrenza con un Semaphore (4-8 richieste concorrenti), rispetta Retry-After e implementa retry con backoff esponenziale. Mantieni sessioni sticky quando il target richiede cookie coerenti. Aggiungi un circuit breaker per sospendere le richieste quando il tasso di errore supera una soglia.

Si può usare SOCKS5 con Ktor Client in Kotlin?

Sì, tramite le proprietà di sistema JVM socksProxyHost, socksProxyPort, java.net.socks.username e java.net.socks.password, oppure configurando un java.net.Proxy di tipo SOCKS con un Authenticator custom. ProxyHat espone SOCKS5 sulla porta 1080 di gate.proxyhat.com.

Verifica la tua configurazione proxy in pochi secondi

Verificatore di proxy gratuito — conferma che i tuoi IP siano veloci, anonimi e non bloccati.

Controlla i proxy gratis
← Torna al Blog