Utiliser des proxies en Kotlin : guide Ktor Client et OkHttp

Guide pratique pour configurer des proxies résidentiels en Kotlin avec Ktor Client et OkHttp, incluant l'authentification proxy, le géo-ciblage, les sessions sticky et le durcissement production.

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

Que vous construisiez un client Android, un scraper backend ou un outil de monitoring SERP, vous finirez tôt ou tard par devoir utiliser des proxies en Kotlin. Les bibliothèques HTTP modernes comme Ktor Client et OkHttp rendent cette tâche relativement simple, mais l'authentification proxy, le géo-ciblage et la rotation d'IP introduisent des subtilités qui font trébucher même les développeurs expérimentés. Ce guide couvre tout, de la configuration de base au durcissement production.

Pourquoi utiliser des proxies en Kotlin avec Ktor et OkHttp

Les proxies HTTP servent d'intermédiaire entre votre application et les serveurs cibles. Pour les développeurs Kotlin, deux cas d'usage dominent : le web scraping (collecte de données publiques à grande échelle) et le test d'APIs géo-restreintes. Dans les deux cas, les serveurs cibles inspectent l'adresse IP source et son ASN (Autonomous System Number). Une IP datacenter comme AWS us-east-1 est immédiatement identifiable et souvent bloquée par les anti-bots modernes.

Les proxies résidentiels résolvent ce problème en routant votre trafic via des adresses IP attribuées par des FAI légitimes. Selon une documentation de référence de Mozilla, un proxy HTTP standard agit au niveau de la couche application et peut nécessiter un en-tête Proxy-Authorization pour l'authentification. La spécification RFC 7235 définit précisément ce mécanisme de défi 407 que nous allons gérer.

Le défi technique en Kotlin est que Ktor Client délègue la gestion du proxy au moteur sous-jacent (CIO, OkHttp, Java). L'authentification proxy n'est donc pas uniforme : le moteur CIO ne gère pas nativement le header Proxy-Authorization, tandis qu'OkHttp fournit une API Authenticator dédiée. Comprendre cette différence est essentiel.

Configuration du projet : Ktor 3 et OkHttp

Dépendances Gradle

Pour un projet Kotlin multiplateforme ou JVM, ajoutez les dépendances Ktor et OkHttp. Voici un extrait pour build.gradle.kts :

dependencies {
    // Ktor Client 3.x avec moteur CIO (JVM)
    implementation("io.ktor:ktor-client-core:3.0.3")
    implementation("io.ktor:ktor-client-cio:3.0.3")
    // Optionnel : moteur OkHttp pour Ktor
    implementation("io.ktor:ktor-client-okhttp:3.0.3")
    // OkHttp standalone (pour comparaison baseline)
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    // Coroutines pour les opérations concurrentes
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

Client Ktor avec moteur CIO

Le moteur CIO (Coroutine-based I/O) est léger et natif Kotlin, mais il ne gère pas l'authentification proxy via java.net.Authenticator. Vous devez injecter manuellement le header Proxy-Authorization :

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64

fun createKtorClientWithProxy(): HttpClient {
    val proxyUser = "user-country-DE-city-berlin"
    val proxyPass = "votre-mot-de-passe"
    val credentials = Base64.getEncoder()
        .encodeToString("$proxyUser:$proxyPass".toByteArray())

    return HttpClient(CIO) {
        engine {
            proxy = Proxy(
                Proxy.Type.HTTP,
                InetSocketAddress("gate.proxyhat.com", 8080)
            )
        }
        defaultRequest {
            header("Proxy-Authorization", "Basic $credentials")
        }
    }
}

suspend fun fetchExample(): String {
    val client = createKtorClientWithProxy()
    return try {
        val response: HttpResponse = client.get("https://httpbin.org/ip")
        response.bodyAsText()
    } finally {
        client.close()
    }
}

Notez l'encodage du nom d'utilisateur : user-country-DE-city-berlin indique à ProxyHat de router via une IP résidentielle située à Berlin, en Allemagne. C'est le géociblage par ville, disponible directement dans le champ username.

Baseline OkHttp avec proxy Java natif

Pour comparaison, voici la même requête avec OkHttp en mode standalone. OkHttp gère l'authentification proxy via un Authenticator dédié :

import okhttp3.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64

fun createOkHttpClientWithProxy(): OkHttpClient {
    val proxyUser = "user-country-DE-city-berlin"
    val proxyPass = "votre-mot-de-passe"
    val proxy = Proxy(
        Proxy.Type.HTTP,
        InetSocketAddress("gate.proxyhat.com", 8080)
    )

    val proxyAuthenticator = Authenticator { _, response ->
        val credential = Credentials.basic(proxyUser, proxyPass)
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    }

    return OkHttpClient.Builder()
        .proxy(proxy)
        .proxyAuthenticator(proxyAuthenticator)
        .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
        .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .build()
}

fun fetchWithOkHttp(): String {
    val client = createOkHttpClientWithProxy()
    val request = Request.Builder()
        .url("https://httpbin.org/ip")
        .build()
    client.newCall(request).execute().use { response ->
        return response.body?.string() ?: ""
    }
}

L'Authenticator d'OkHttp est invoqué uniquement en cas de défi 407, ce qui évite d'envoyer les credentials inutilement. C'est plus propre que l'injection manuelle du header, mais nécessite le moteur OkHttp.

Routage via gate.proxyhat.com : géociblage et sessions sticky

Géociblage dans le nom d'utilisateur

ProxyHat encode les paramètres de routage directement dans le champ username, séparés par des tirets. Voici les formats courants :

ObjectifFormat du usernameExemple
Pays uniquementuser-country-{CODE}user-country-US
Pays + villeuser-country-{CODE}-city-{ville}user-country-FR-city-paris
Session stickyuser-session-{id}user-session-abc123
Combinéuser-country-DE-session-xyzIP fixe pour cette session

Les sessions sticky maintiennent la même IP résidentielle pour toutes les requêtes d'une session donnée — utile pour les sites qui nécessitent une cohérence d'IP (login, panier d'achat). Sans session, ProxyHat attribue une nouvelle IP à chaque requête.

Exemple Ktor avec session sticky

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64

suspend fun fetchWithStickySession(sessionId: String): String {
    val proxyUser = "user-country-US-session-$sessionId"
    val proxyPass = "votre-mot-de-passe"
    val credentials = Base64.getEncoder()
        .encodeToString("$proxyUser:$proxyPass".toByteArray())

    val client = HttpClient(CIO) {
        engine {
            proxy = Proxy(
                Proxy.Type.HTTP,
                InetSocketAddress("gate.proxyhat.com", 8080)
            )
        }
        defaultRequest {
            header("Proxy-Authorization", "Basic $credentials")
        }
    }

    return try {
        // Toutes les requêtes de ce client utilisent la même IP
        val r1 = client.get("https://httpbin.org/ip").bodyAsText()
        val r2 = client.get("https://httpbin.org/headers").bodyAsText()
        "$r1\n$r2"
    } finally {
        client.close()
    }
}

SOCKS5 sur le port 1080

ProxyHat supporte également SOCKS5 sur le port 1080. En Kotlin/JVM, la méthode la plus simple consiste à utiliser les propriétés système Java pour l'authentification SOCKS :

import java.net.Authenticator
import java.net.PasswordAuthentication
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*

fun configureSocks5Proxy() {
    // Définir les propriétés système pour SOCKS5
    System.setProperty("socksProxyHost", "gate.proxyhat.com")
    System.setProperty("socksProxyPort", "1080")
    System.setProperty("java.net.socks.username", "user-country-GB")
    System.setProperty("java.net.socks.password", "votre-mot-de-passe")

    // Configurer l'Authenticator JVM pour SOCKS5
    Authenticator.setDefault(object : Authenticator() {
        override fun getPasswordAuthentication(): PasswordAuthentication {
            return PasswordAuthentication(
                System.getProperty("java.net.socks.username"),
                System.getProperty("java.net.socks.password")?.toCharArray()
            )
        }
    })
}

suspend fun fetchViaSocks5(): String {
    configureSocks5Proxy()
    val client = HttpClient(CIO)
    return try {
        val response = client.get("https://httpbin.org/ip")
        response.bodyAsText()
    } finally {
        client.close()
    }
}

Attention : les propriétés système sont globales à la JVM. Cette approche ne convient pas si vous devez utiliser différents proxies SOCKS5 simultanément. Pour ce cas, préférez le moteur OkHttp de Ktor qui permet de configurer un java.net.Proxy(Proxy.Type.SOCKS, ...) par client.

Pourquoi les proxies résidentiels sont indispensables

Les applications mobiles et les réseaux sociaux (Instagram, TikTok, LinkedIn) déploient des anti-bots sophistiqués qui croisent l'ASN de l'IP source avec des bases de données comme les registres RIR. Une IP AWS (AS14618), Google Cloud (AS15169) ou OVH (AS16276) est immédiatement classée comme datacenter et souvent bloquée ou soumise à un CAPTCHA. Les proxies résidentiels portent un ASN de FAI réel (ex. AS3215 pour Orange France), ce qui les rend indétectables.

Fan-out concurrent avec coroutines et Semaphore

Voici un exemple complet de scraping concurrent avec contrôle de débit. On utilise async/awaitAll pour paralléliser et un Semaphore pour limiter la concurrence à 50 requêtes simultanées :

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64

data class ScrapeResult(val url: String, val status: Int, val body: String)

suspend fun scrapeConcurrently(urls: List<String>): List<ScrapeResult> = coroutineScope {
    val semaphore = Semaphore(50) // Max 50 requêtes concurrentes
    val proxyUser = "user-country-US"
    val proxyPass = "votre-mot-de-passe"
    val credentials = Base64.getEncoder()
        .encodeToString("$proxyUser:$proxyPass".toByteArray())

    val client = HttpClient(CIO) {
        engine {
            proxy = Proxy(
                Proxy.Type.HTTP,
                InetSocketAddress("gate.proxyhat.com", 8080)
            )
        }
        defaultRequest {
            header("Proxy-Authorization", "Basic $credentials")
        }
    }

    try {
        urls.map { url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    try {
                        val response = client.get(url)
                        ScrapeResult(
                            url = url,
                            status = response.status.value,
                            body = response.bodyAsText()
                        )
                    } catch (e: Exception) {
                        ScrapeResult(url, -1, e.message ?: "error")
                    }
                }
            }
        }.awaitAll()
    } finally {
        client.close()
    }
}

// Utilisation
suspend fun main() {
    val targets = (1..100).map { "https://httpbin.org/delay/1?page=$it" }
    val results = scrapeConcurrently(targets)
    val successCount = results.count { it.status == 200 }
    println("Succès : $successCount / ${results.size}")
}

Avec un Semaphore(50) et des cibles à latence ~200ms, ce pattern peut traiter environ 250 requêtes/seconde sur une seule machine. Ajustez la taille du semaphore selon les limites de votre plan ProxyHat et la capacité de votre machine.

Durcissement production

Gestion des défis 407 avec OkHttp Authenticator

En production, le proxy peut renvoyer un défi 407 (Proxy Authentication Required) en cas d'expiration de session ou de changement d'IP. L'Authenticator d'OkHttp gère cela automatiquement, mais vous devez limiter le nombre de tentatives pour éviter les boucles infinies :

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

fun createProductionOkHttp(): OkHttpClient {
    val proxyUser = "user-country-FR-session-prod-001"
    val proxyPass = "votre-mot-de-passe"

    val proxyAuthenticator = Authenticator { _, response ->
        // Éviter les boucles infinies : max 3 tentatives
        if (response.priorResponse != null &&
            response.priorResponse!!.priorResponse != null) {
            return@Authenticator null
        }
        val credential = Credentials.basic(proxyUser, proxyPass)
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    }

    return OkHttpClient.Builder()
        .proxy(Proxy(
            Proxy.Type.HTTP,
            InetSocketAddress("gate.proxyhat.com", 8080)
        ))
        .proxyAuthenticator(proxyAuthenticator)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(15, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(ConnectionPool(20, 5, TimeUnit.MINUTES))
        .build()
}

Configuration TLS

Pour les connexions HTTPS via proxy, OkHttp et Ktor utilisent le tunnel CONNECT. Assurez-vous que votre TrustManager valide les certificats du serveur cible (et non ceux du proxy) :

import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
import javax.net.ssl.X509TrustManager
import javax.net.ssl.SSLContext
import java.security.cert.X509Certificate

fun createTlsOkHttp(): OkHttpClient {
    // TrustManager standard qui valide les certificats du serveur cible
    val trustManager = object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
        override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
            // Validation standard — ne pas désactiver en production
            chain.forEach { it.checkValidity() }
        }
        override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
    }

    val sslContext = SSLContext.getInstance("TLSv1.2")
    sslContext.init(null, arrayOf(trustManager), null)

    return OkHttpClient.Builder()
        .sslSocketFactory(sslContext.socketFactory, trustManager)
        .connectTimeout(10, TimeUnit.SECONDS)
        .build()
}

Notes Android : NetworkSecurityConfig

Sur Android 9+ (API 28+), le trafic en clair (HTTP non-HTTPS) est bloqué par défaut. Si vous devez accéder à des cibles HTTP via le proxy, ajoutez une exception dans res/xml/network_security_config.xml :

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">gate.proxyhat.com</domain>
        <!-- Uniquement pour les cibles HTTP non-HTTPS -->
        <domain includeSubdomains="false">api.legacy-target.com</domain>
    </domain-config>
</network-security-config>

Référencez ce fichier dans le AndroidManifest.xml :

<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

Pattern SDK ProxyHat

Le SDK ProxyHat (documentation officielle) encapsule ce pattern d'authentification proxy. Il gère la rotation d'IP, les sessions sticky et le géociblage via une API Kotlin idiomatique. Le principe reste identique : le nom d'utilisateur encode les paramètres de routage, et l'authentification se fait via Proxy-Authorization: Basic. Consultez la page de tarification pour les limites de concurrence par plan, et la liste des localisations pour les codes pays et villes disponibles.

Considérations éthiques et légales

Le web scraping n'est pas illégal en soi, mais il se situe à l'intersection de plusieurs cadres juridiques :

  • États-Unis — CFAA : Le Computer Fraud and Abuse Act criminalise l'accès non autorisé à des systèmes protégés. La jurisprudence hiQ Labs v. LinkedIn (2022) a partiellement clarifié que scraper des données publiques n'enfreint pas la CFAA, mais le paysage reste en évolution.
  • UE — RGPD : Le Règlement Général sur la Protection des Données s'applique aux données personnelles collectées. Si vous scrapez des profils utilisateurs (réseaux sociaux, annuaires), vous devez avoir une base légale et respecter les droits des personnes concernées.
  • robots.txt : Respecter le fichier robots.txt d'un site est une bonne pratique, même si son statut juridique varie selon les juridictions.
  • Conditions d'utilisation (ToS) : De nombreux sites interdisent le scraping dans leurs ToS. Une violation peut entraîner la résiliation du compte et des poursuites civiles.

Préférez toujours les API officielles quand elles existent. Pour le suivi SERP, consultez nos cas d'usage SERP tracking ; pour le scraping web à grande échelle, notre guide de scraping web détaille les bonnes pratiques.

Points clés à retenir

  • Moteur Ktor = comportement proxy différent : CIO nécessite un header Proxy-Authorization manuel dans defaultRequest ; OkHttp fournit un Authenticator dédié.
  • Géociblage dans le username : user-country-DE-city-berlin route via une IP résidentielle à Berlin. Les sessions sticky ajoutent -session-{id}.
  • SOCKS5 = port 1080 : Utilisez les propriétés système java.net.socks.username/password ou un java.net.Proxy(Type.SOCKS, ...) par client.
  • Concurrence contrôlée : Semaphore + async/awaitAll pour paralléliser sans saturer le proxy ni la machine.
  • Durcissement : Limitez les retries 407, configurez les timeouts, utilisez un ConnectionPool, et validez TLS côté cible.
  • Éthique : Données publiques uniquement, respectez robots.txt et le RGPD, préférez les API officielles.

FAQ

Qu'est-ce que l'utilisation de proxies en Kotlin ?

L'utilisation de proxies en Kotlin consiste à configurer un client HTTP (Ktor Client ou OkHttp) pour router le trafic via un serveur intermédiaire, typiquement pour masquer l'IP source, géocibler les requêtes ou contourner les restrictions de taux. En Kotlin/JVM, cela se fait via java.net.Proxy pour le moteur, et via le header Proxy-Authorization: Basic ou un Authenticator pour l'authentification.

Pourquoi l'utilisation de proxies en Kotlin est-elle importante ?

Les proxies sont essentiels pour le web scraping à grande échelle, le test d'APIs géo-restreintes et l'accès à des cibles qui bloquent les ASNs datacenter. Sans proxy, votre IP de serveur (AWS, GCP, OVH) est identifiable et souvent bloquée dès les premières requêtes. Les proxies résidentiels portent un ASN de FAI réel, ce qui les rend indétectables par les anti-bots modernes.

Quel type de proxy fonctionne le mieux en Kotlin ?

Les proxies résidentiels sont le meilleur choix pour les cibles qui bloquent les IPs datacenter (réseaux sociaux, e-commerce, SERP). Les proxies datacenter conviennent pour les tâches moins sensibles (tests d'API internes, monitoring simple). Les proxies mobiles offrent le plus haut niveau de confiance (ASN mobile FAI) mais sont plus coûteux. ProxyHat propose les trois types via le même endpoint gate.proxyhat.com:8080.

Comment éviter les blocages en utilisant des proxies en Kotlin ?

Utilisez des proxies résidentiels (ASN FAI), faites tourner les IP avec des sessions uniques par requête (-session-{id}), limitez la concurrence avec un Semaphore (50 requêtes simultanées max), ajoutez des délais aléatoires entre les requêtes, et géérez les défis 407 avec un Authenticator limité à 3 tentatives. Respectez aussi robots.txt et les limites de débit de la cible.

Comment configurer l'authentification proxy avec Ktor CIO ?

Le moteur CIO de Ktor ne gère pas nativement l'authentification proxy. Vous devez encoder les credentials en Base64 et injecter le header Proxy-Authorization: Basic {credentials} dans le bloc defaultRequest. Le format du username encode le géociblage : user-country-FR-session-abc123. Utilisez le moteur OkHttp de Ktor si vous préférez l'approche Authenticator automatique.

Prêt à commencer ?

Accédez à plus de 50M d'IPs résidentielles dans plus de 148 pays avec filtrage IA.

Voir les tarifsProxies résidentiels
← Retour au Blog