Korzystanie z proxy w Kotlinie: Przewodnik dla programistów z Ktor i OkHttp

Kompletny przewodnik po konfiguracji proxy w Kotlinie z Ktor Client 3 i OkHttp — od podstawowej integracji z gate.proxyhat.com:8080, przez geo-targeting i sesje sticky, po współbieżne scraping z koryutynami i hardening produkcyjny.

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

Dlaczego korzystanie z proxy w Kotlinie jest niezbędne

Korzystanie z proxy w Kotlinie to dziś fundament każdego poważnego projektu scrapingowego, automatyzacji testów QA czy monitorowania cen. Twój serwer lub aplikacja Android wysyła żądania z jednego adresu IP — a docelowe serwisy, od platform e-commerce po API społecznościowe, coraz agresywniej blokują ruch z zakresów ASN przypisanych do centrów danych. Według danych specyfikacji MDN dotyczącej nagłówka Forwarded, serwery mogą identyfikować i odrzucać żądania na podstawie sygnatur IP, TLS i zachowania klienta. Bez warstwy proxy Twoje zapytania z Kotlin mogą zostać odrzucone już po kilkudziesięciu żądaniach.

W tym przewodniku pokażemy, jak skonfigurować Ktor client proxy oraz uwierzytelnianie proxy OkHttp z bramką ProxyHat (gate.proxyhat.com:8080), jak kodować geo-targeting w nazwie użytkownika, jak używać SOCKS5 na porcie 1080 i jak zabezpieczyć scraping na produkcji. Każdy przykład jest gotowy do uruchomienia.

Kontekst techniczny: dlaczego proxy residential zamiast datacenter

Adresy IP z centrów danych (datacenter) są łatwe do sklasyfikowania — bazy danych typu Autonomous System Number (ASN) publicznie mapują bloki IP do organizacji takich jak AWS, Google Cloud czy OVH. Serwery antybotowe (np. Cloudflare, Akamai Bot Manager) używają tych danych, aby odrzucić ruch z ASN datacenter zanim żądanie dotrze do logiki aplikacji. Proxy residential omijają ten problem, ponieważ pochodzą od rzeczywistych dostawców ISP.

Dla programistów Kotlin oznacza to wybór między:

  • Datacenter proxy — szybkie (latencja ~10–30 ms), ale łatwo blokowane przez app/social targets.
  • Residential proxy — wolniejsze (~200–500 ms), ale z adresów ISP, omijają blokady ASN.
  • Mobile proxy — najdroższe, ale najtrudniejsze do wykrycia; idealne dla celów mobilnych.
Typ proxyŚrednia latencjaWskaźnik sukcesu (app targets)Koszt
Datacenter10–30 ms~30–50%Niski
Residential200–500 ms~90–95%Średni
Mobile300–800 ms~95–99%Wysoki

Więcej o dostępnych lokalizacjach znajdziesz na stronie lokalizacji ProxyHat.

Krok 1: Konfiguracja projektu — Ktor 3 z silnikiem CIO/OkHttp

Zacznijmy od dodania zależności w build.gradle.kts. Ktor 3 wspiera silnik OkHttp, który natywnie obsługuje proxy HTTP i SOCKS5.

// build.gradle.kts
val ktorVersion = "3.0.3"

dependencies {
    implementation("io.ktor:ktor-client-core:$ktorVersion")
    implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
    implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

Podstawowy klient Ktor z silnikiem OkHttp wygląda tak:

import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.net.InetSocketAddress
import java.net.Proxy

fun createKtorClient(): HttpClient {
    return HttpClient(OkHttp) {
        engine {
            config {
                proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
            }
        }
    }
}

Bazowa konfiguracja OkHttp bez Ktor

Jeśli wolisz używać OkHttp bezpośrednio — np. w aplikacji Android, gdzie OkHttp jest już standardem — konfiguracja proxy jest równie prosta:

import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy

fun createOkHttpProxyClient(): OkHttpClient {
    val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
    return OkHttpClient.Builder()
        .proxy(proxy)
        .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
        .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .build()
}

Krok 2: Routing przez gate.proxyhat.com z geo-targetingiem i sesjami sticky

ProxyHat koduje parametry geo-targetingu i sesji bezpośrednio w nazwie użytkownika. Format to user-country-DE-city-berlin-session-abc123. Ponieważ uwierzytelnianie proxy jest specyficzne dla silnika, w Ktor musimy dodać nagłówek Proxy-Authorization: Basic w defaultRequest.

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

fun createGeoTargetedClient(
    country: String = "DE",
    city: String = "berlin",
    sessionId: String = "abc123"
): HttpClient {
    val username = "user-country-$country-city-$city-session-$sessionId"
    val password = "twoje_haslo"
    val credentials = "$username:$password"
    val encoded = Base64.getEncoder().encodeToString(credentials.toByteArray())

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

suspend fun fetchWithGeo() {
    val client = createGeoTargetedClient("DE", "berlin", "sess-001")
    val response: HttpResponse = client.get("https://httpbin.org/ip")
    println(response.bodyAsText())
    client.close()
}

W czystym OkHttp uwierzytelnianie proxy realizuje się przez Authenticator:

import okhttp3.Authenticator
import okhttp3.Credentials
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy

fun createOkHttpWithProxyAuth(): OkHttpClient {
    val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
    val username = "user-country-DE-city-berlin-session-abc123"
    val password = "twoje_haslo"

    val proxyAuth = Authenticator { _: Route?, response ->
        val credential = Credentials.basic(username, password)
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    }

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

Wskazówka: Sesje sticky (-session-abc123) utrzymują ten sam adres IP wychodzący przez całą sesję. To kluczowe dla scraping stron, które śledzą spójność IP między żądaniami (np. logowanie, koszyki e-commerce).

Krok 3: SOCKS5 na porcie 1080 przez właściwości systemowe

SOCKS5 działa na niższej warstwie niż HTTP proxy i jest obsługiwany przez JVM natywnie. Dla ProxyHat używamy portu 1080. W Kotlinie najprościej ustawić SOCKS5 przez właściwości systemowe java.net.socks.username i java.net.socks.password, ponieważ standardowy java.net.Proxy nie przekazuje poświadczeń SOCKS5.

import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.net.InetSocketAddress
import java.net.Proxy

fun createSocks5Client(): HttpClient {
    // Ustaw poświadczenia SOCKS5 dla JVM
    System.setProperty("java.net.socks.username", "user-country-DE-city-berlin")
    System.setProperty("java.net.socks.password", "twoje_haslo")

    return HttpClient(OkHttp) {
        engine {
            config {
                proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress("gate.proxyhat.com", 1080)))
            }
        }
    }
}

suspend fun fetchViaSocks5() {
    val client = createSocks5Client()
    val response = client.get("https://httpbin.org/ip")
    println(response.bodyAsText())
    client.close()
}

Uwaga: Właściwości systemowe SOCKS5 są globalne dla JVM. W aplikacjach wielowątkowych lub serwerach współużytkujących jedną maszynę wirtualną rozważ uruchomienie osobnego procesu lub użycie bibliotek takich jak OkHttp z niestandardowym SSLSocketFactory i ProxySelector.

Krok 4: Residential proxy dla targetów app/social — współbieżny scraping z koryutynami

Platformy społecznościowe i aplikacje mobilne blokują adresy IP z ASN datacenter niemal natychmiast. Residential proxy są tu niezbędne. Poniższy przykład pokazuje, jak rozproszyć współbieżne żądania przez wiele sesji proxy, używając async/awaitAll z Semaphore do kontroli tempa.

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

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

fun createSessionClient(sessionId: String, country: String = "DE"): HttpClient {
    val username = "user-country-$country-session-$sessionId"
    val password = "twoje_haslo"
    val encoded = Base64.getEncoder()
        .encodeToString("$username:$password".toByteArray())

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

suspend fun fanOutScrape(urls: List<String>, maxConcurrency: Int = 10) {
    val semaphore = Semaphore(maxConcurrency)
    val results = coroutineScope {
        urls.mapIndexed { index, url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    val sessionId = "sess-${index % 20}" // 20 unikalnych sesji IP
                    val client = createSessionClient(sessionId)
                    try {
                        val response = client.get(url)
                        ScrapeResult(url, response.status.value, response.bodyAsText())
                    } catch (e: Exception) {
                        ScrapeResult(url, -1, e.message ?: "error")
                    } finally {
                        client.close()
                    }
                }
            }
        }.awaitAll()
    }
    results.forEach { println("${it.status} — ${it.url}") }
}

fun main() = runBlocking {
    val urls = (1..50).map { "https://httpbin.org/ip?req=$it" }
    fanOutScrape(urls, maxConcurrency = 10)
}

W tym wzorcu każde żądanie otrzymuje własnego klienta z unikalnym sessionId, co skutkuje różnymi adresami IP wychodzącymi. Semaphore(10) ogranicza współbieżność do 10 jednoczesnych żądań, co zapobiega przeciążeniu bramki proxy i zmniejsza ryzyko rate-limitów. Więcej przypadków użycia znajdziesz na stronie web scraping oraz SERP tracking.

Krok 5: Hardening produkcyjny — retries, pooling, TLS i Android

OkHttp Authenticator dla wyzwań 407

Gdy serwer proxy zwraca 407 Proxy Authentication Required, OkHttp automatycznie ponawia żądanie z poświadczeniami, jeśli zarejestrujesz proxyAuthenticator. Pokazaliśmy to w Kroku 2. Dodatkowo warto zaimplementować Interceptor do logowania i retry z exponential backoff:

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

fun createProductionOkHttp(): OkHttpClient {
    val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
    val username = "user-country-DE-session-prod01"
    val password = "twoje_haslo"

    val retryInterceptor = Interceptor { chain ->
        var attempt = 0
        var lastError: IOException? = null
        while (attempt < 3) {
            try {
                return@Interceptor chain.proceed(chain.request())
            } catch (e: IOException) {
                lastError = e
                attempt++
                val delay = (200L * (1 shl attempt)) // 400ms, 800ms, 1600ms
                Thread.sleep(delay)
            }
        }
        throw lastError ?: IOException("max retries exceeded")
    }

    return OkHttpClient.Builder()
        .proxy(proxy)
        .proxyAuthenticator { _, response ->
            response.request.newBuilder()
                .header("Proxy-Authorization", Credentials.basic(username, password))
                .build()
        }
        .addInterceptor(retryInterceptor)
        .connectionPool(ConnectionPool(20, 5, TimeUnit.MINUTES))
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(15, TimeUnit.SECONDS)
        .build()
}

Konfiguracja TLS

W środowiskach z restrykcyjnymi firewallami może być konieczne dostrojenie TLS. OkHttp domyślnie używa ConnectionSpec.MODERN_TLS, ale możesz wymusić konkretne wersje protokołu:

import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.TlsVersion
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import java.security.KeyStore

fun configureTls(builder: OkHttpClient.Builder): OkHttpClient.Builder {
    val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3)
        .build()

    return builder.connectionSpecs(listOf(spec))
}

Android: NetworkSecurityConfig

Na Androidzie od API 28 (Android 9) domyślnie blokowany jest ruch HTTP (cleartext). Jeśli Twoja aplikacja łączy się z bramką proxy przez HTTP (nie HTTPS do proxy, lecz tunelowanie HTTPS przez proxy HTTP), upewnij się, że network_security_config.xml zawiera odpowiedni wyjątek:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">gate.proxyhat.com</domain>
    </domain-config>
</network-security-config>

Dodaj odniesienie w AndroidManifest.xml: android:networkSecurityConfig="@xml/network_security_config".

Krok 6: Etyczny scraping — publiczne dane, CFAA, GDPR i oficjalne API

Web scraping w Kotlinie daje potężne możliwości, ale wiąże się z odpowiedzialnością prawną. W Stanach Zjednoczonych Computer Fraud and Abuse Act (CFAA) kryminalizuje nieautoryzowany dostęp do systemów komputerowych — choć orzeczenie Van Buren v. United States (2021) zawęziło definicję „nieautoryzowanego dostępu”, scraping danych za paywall lub z naruszeniem regulaminu (ToS) nadal niesie ryzyko. W Unii Europejskiej RODO (GDPR) ogranicza przetwarzanie danych osobowych — adresy IP są danymi osobowymi zgodnie z art. 4 pkt 1.

Best practices:

  • Scrapuj tylko publicznie dostępne dane — nic za loginem, paywallem ani w strefach chronionych.
  • Szanuj robots.txt — traktuj go jako sygnał, nawet jeśli nie jest prawnie wiążący.
  • Preferuj oficjalne API — jeśli platforma oferuje API, użyj go zamiast scrapingu.
  • Limit tempa — utrzymuj rozsądne opóźnienia (np. 1–5 żądań/sekundę na sesję).
  • Nie przechowuj danych osobowych dłużej niż to konieczne.

Szczegóły implementacji ProxyHat znajdziesz w oficjalnej dokumentacji ProxyHat, a cennik na stronie cennika ProxyHat.

Key Takeaways

  • Ktor client proxy wymaga nagłówka Proxy-Authorization: Basic w defaultRequest, ponieważ uwierzytelnianie proxy jest specyficzne dla silnika.
  • OkHttp obsługuje proxy auth natywnie przez proxyAuthenticator — to preferowane podejście na Androidzie.
  • Geo-targeting i sesje sticky koduje się w nazwie użytkownika: user-country-DE-city-berlin-session-abc123.
  • SOCKS5 na porcie 1080 wymaga ustawienia java.net.socks.username/password jako właściwości systemowych JVM.
  • Residential proxy są niezbędne dla targetów app/social — datacenter ASN są blokowane w ~50–70% przypadków.
  • Produkcyjny scraping wymaga retry z exponential backoff, connection poolingu i kontroli współbieżności (Semaphore).
  • Etyczny scraping ogranicza się do publicznych danych z poszanowaniem CFAA, GDPR i regulaminów platform.

Często zadawane pytania

Czym jest korzystanie z proxy w Kotlinie?

Korzystanie z proxy w Kotlinie polega na konfiguracji klientów HTTP (Ktor, OkHttp) tak, aby kierowały ruch przez pośredni serwer proxy, ukrywając rzeczywisty adres IP aplikacji. W Kotlinie używa się java.net.Proxy dla połączeń HTTP/SOCKS5 oraz nagłówka Proxy-Authorization dla uwierzytelniania.

Dlaczego korzystanie z proxy w Kotlinie ma znaczenie dla użytkowników proxy?

Bez proxy aplikacje Kotlin wysyłają żądania z jednego IP, co prowadzi do szybkiego zablokowania przez systemy antybotowe. Proxy residential omijają blokady ASN datacenter, umożliwiając scraping, monitorowanie cen i testy QA na skalę.

Który typ proxy najlepiej nadaje się do korzystania z proxy w Kotlinie?

Do targetów app i społecznościowych najlepiej sprawdzają się proxy residential (wskaźnik sukcesu ~90–95%). Do szybkich zadań, gdzie blokady nie są problemem, wystarczą datacenter proxy. Mobile proxy są optymalne dla aplikacji mobilnych, ale najdroższe.

Jak uniknąć blokad przy implementacji korzystania z proxy w Kotlinie?

Używaj sesji sticky dla spójności IP, rotuj sessionId między żądaniami, ogranicz współbieżność przez Semaphore, implementuj retry z exponential backoff i szanuj robots.txt. Unikaj ASN datacenter dla targetów, które je blokują.

Gotowy, aby zacząć?

Dostęp do ponad 50 mln rezydencjalnych IP w ponad 148 krajach z filtrowaniem AI.

Zobacz cenyProxy rezydencjalne
← Powrót do Bloga