Использование прокси в Kotlin — это базовый навык для Android- и backend-разработчиков, которым нужно собирать публичные данные, тестировать геозависимый контент или обходить ограничения по IP. В этом руководстве мы пройдём от минимального примера с OkHttpClient до полноценного скрапера на Ktor 3 с residential-прокси ProxyHat, geo-таргетингом, sticky-сессиями и конкурентными запросами на корутинах.
Зачем нужен Kotlin прокси и почему residential
Kotlin прокси — это не просто «прокинуть трафик через другой IP». Тип прокси определяет, пройдёт ли ваш запрос через антибот-системы. Datacenter-прокси выдают себя ASN-ами облачных провайдеров (AWS, Hetzner, OVH), которые крупные сайты и социальные платформы блокируют по умолчанию. Residential-прокси используют IP реальных домашних провайдеров, поэтому выглядят как обычные пользователи.
По данным Imperva Bad Bot Report 2024, на автоматизированный трафик приходится более 40% всего интернет-трафика, и антибот-системы всё чаще классифицируют запросы по репутации ASN. Если ваша цель — SERP, социальные сети или e-commerce, residential-прокси дают значительно более высокий success rate, чем datacenter.
Mobile-прокси — подмножество residential, где IP принадлежат мобильным операторам. Они ещё довереннее, но дороже. Datacenter-прокси остаются оптимальными для задач, где блокировка по ASN не критична: внутренний мониторинг, QA-тестирование, парсинг открытых API.
Технический контекст: как прокси-аутентификация работает в JVM
В JVM-стеке прокси-аутентификация — это не тривиальный HTTP-заголовок, а отдельный механизм. Когда HTTP-клиент подключается к прокси-серверу, прокси может ответить 407 Proxy Authentication Required с заголовком Proxy-Authenticate. Клиент должен ответить заголовком Proxy-Authorization. Для HTTP-прокси это обычно Basic-аутентификация, для SOCKS5 — встроенный в протокол механизм username/password (см. RFC 1929).
Проблема в том, что каждый HTTP-движок в Kotlin/JVM обрабатывает это по-своему:
- OkHttp — поддерживает
Authenticatorдля прокси-челленджей черезproxyAuthenticator(). - Ktor CIO engine — не имеет встроенного прокси-аутентификатора, заголовок нужно добавлять вручную.
- Ktor OkHttp engine — делегирует в OkHttp, можно использовать нативный
proxyAuthenticator. - java.net.Proxy + SOCKS5 — требует системных свойств
java.net.socks.usernameиjava.net.socks.password.
Поэтому универсального «одного способа» нет — подход зависит от движка и типа прокси.
Настройка проекта: Ktor 3 и OkHttp
Добавьте зависимости в build.gradle.kts:
// build.gradle.kts
implementation("io.ktor:ktor-client-core:3.0.3")
implementation("io.ktor:ktor-client-cio:3.0.3")
implementation("io.ktor:ktor-client-okhttp:3.0.3")
implementation("io.ktor:ktor-client-content-negotiation:3.0.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
Базовый OkHttpClient с прокси
Начнём с сырого OkHttp — это самый прозрачный способ увидеть, как прокси-аутентификация работает:
import okhttp3.Authenticator
import okhttp3.Credentials
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
fun buildOkHttpProxyClient(
proxyHost: String = "gate.proxyhat.com",
proxyPort: Int = 8080,
username: String,
password: String
): OkHttpClient {
val proxyAuth = Authenticator { _, response ->
val credential = Credentials.basic(username, password)
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
return OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort)))
.proxyAuthenticator(proxyAuth)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
}
fun main() {
val client = buildOkHttpProxyClient(
username = "user-country-DE-city-berlin",
password = "YOUR_PASSWORD"
)
val request = Request.Builder()
.url("https://httpbin.org/ip")
.build()
client.newCall(request).execute().use { response ->
println(response.code)
println(response.body?.string())
}
}
Здесь user-country-DE-city-berlin — это geo-таргетинг, закодированный в username. ProxyHat читает флаги из username, поэтому вам не нужны отдельные параметры. Для sticky-сессии добавьте -session-abc123: user-country-DE-city-berlin-session-abc123.
Ktor 3 HttpClient на CIO engine
Ktor CIO — чисто Kotlin-движок без зависимости от OkHttp. Удобен для серверных и кроссплатформенных проектов. Но CIO не поддерживает автоматический Proxy-Authorization, поэтому заголовок нужно добавлять вручную через defaultRequest:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64
fun buildKtorCioProxyClient(
username: String,
password: String
): HttpClient {
val proxyAuthHeader = "Basic " +
Base64.getEncoder().encodeToString("$username:$password".toByteArray())
return HttpClient(CIO) {
engine {
proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
}
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
defaultRequest {
header("Proxy-Authorization", proxyAuthHeader)
header(HttpHeaders.UserAgent, "ProxyHat-Kotlin/1.0")
}
install(HttpTimeout) {
connectTimeoutMillis = 15_000
requestTimeoutMillis = 30_000
socketTimeoutMillis = 30_000
}
}
}
suspend fun fetchIp(client: HttpClient): String {
val resp = client.get("https://httpbin.org/ip")
return resp.bodyAsText()
}
suspend fun main() {
val client = buildKtorCioProxyClient(
username = "user-country-US-session-sess-001",
password = "YOUR_PASSWORD"
)
client.use {
println(fetchIp(it))
}
}
Важно: Proxy-Authorization в defaultRequest добавляется к каждому запросу. Это работает, потому что HTTP-прокси-аутентификация в Basic-форме — stateless: прокси проверяет заголовок на каждый запрос.
Geo-таргетинг и sticky-сессии
ProxyHat кодирует параметры в username по схеме user-{flag}-{value}. Основные флаги:
| Флаг | Пример | Назначение |
|---|---|---|
country | user-country-DE | Страна по ISO 3166-1 alpha-2 |
city | user-country-DE-city-berlin | Город (в нижнем регистре) |
session | user-session-abc123 | Sticky-сессия — один IP на все запросы |
| комбинация | user-country-DE-city-berlin-session-abc123 | Geo + sticky в одной строке |
Sticky-сессии критичны для сайтов с сессионным state: корзины, логины, многошаговые формы. Без sticky каждый запрос получит новый IP, и сессия «слетит». Срок жизни sticky-сессии у ProxyHat — до 30 минут в зависимости от тарифа.
SOCKS5 на порту 1080
SOCKS5 работает на транспортном уровне, не модифицируя HTTP-заголовки. Это даёт два преимущества: прокси не видит содержимое запросов (только адрес назначения) и работает с любым TCP-протоколом. В JVM SOCKS5-аутентификация настраивается через системные свойства:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.net.InetSocketAddress
import java.net.Proxy
fun configureSocks5(username: String, password: String) {
// SOCKS5 credentials для JVM
System.setProperty("java.net.socks.username", username)
System.setProperty("java.net.socks.password", password)
}
suspend fun main() {
configureSocks5("user-country-GB", "YOUR_PASSWORD")
val client = HttpClient(CIO) {
engine {
proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress("gate.proxyhat.com", 1080))
}
}
client.use {
val resp = it.get("https://httpbin.org/ip")
println(resp.bodyAsText())
}
}
Внимание: системные свойства
java.net.socks.usernameиjava.net.socks.passwordглобальны для всей JVM. В многопоточных приложениях с разными credentials это создаёт гонку. Для production используйте OkHttp с кастомнымAuthenticatorили Ktor OkHttp engine.
Почему residential-прокси нужны для социальных и app-таргетов
Социальные сети (Instagram, TikTok, X) и крупные приложения используют многоуровневую защиту: IP-репутация по ASN, TLS-fingerprinting (JA3/JA4), поведенческий анализ, CAPTCHA. Datacenter-IP блокируются на первом уровне — запрос даже не доходит до контента.
Residential-прокси проходят первый барьер, потому что их ASN принадлежат реальным ISP (Comcast, Deutsche Telekom, Vodafone). Но это не серебряная пуля — частота запросов и TLS-fingerprint всё равно важны. См. наш разбор SERP-трекинга для деталей.
Конкурентный скрапинг на корутинах с Semaphore
Когда нужно собрать 1000 страниц, последовательные запросы слишком медленные. Но параллельная отправка всех 1000 запросов приведёт к блокировке по rate limit. Решение — async + awaitAll с Semaphore для ограничения конкурентности:
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
import kotlin.random.Random
suspend fun scrapeConcurrent(
urls: List<String>,
proxyUser: String,
proxyPass: String,
concurrency: Int = 20
): List<Result<String>> = coroutineScope {
val authHeader = "Basic " +
Base64.getEncoder().encodeToString("$proxyUser:$proxyPass".toByteArray())
val client = HttpClient(CIO) {
engine {
proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080))
}
install(HttpTimeout) {
connectTimeoutMillis = 10_000
requestTimeoutMillis = 20_000
}
defaultRequest {
header("Proxy-Authorization", authHeader)
}
}
val semaphore = Semaphore(concurrency)
val results = urls.map { url ->
async(Dispatchers.IO) {
semaphore.withPermit {
try {
val resp = client.get(url)
if (resp.status.value in 200..299) {
Result.success(resp.bodyAsText())
} else {
Result.failure(RuntimeException("HTTP ${resp.status.value}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
}
val all = results.awaitAll()
client.close()
all
}
suspend fun main() = runBlocking {
val urls = (1..50).map { "https://httpbin.org/delay/${Random.nextInt(1, 3)}" }
val results = scrapeConcurrent(
urls = urls,
proxyUser = "user-country-US",
proxyPass = "YOUR_PASSWORD",
concurrency = 15
)
val success = results.count { it.isSuccess }
println("Success: $success / ${urls.size}")
}
Semaphore(15) гарантирует, что одновременно выполняется не более 15 запросов. Это даёт баланс между скоростью и аккуратностью — слишком высокая конкурентность триггерит anti-bot, слишком низкая — медленный сбор. Для residential-прокси оптимальный диапазон обычно 10–30 параллельных запросов на одну сессию; ротация IP между запросами позволяет поднять этот потолок.
Production-харденинг
OkHttp Authenticator для 407-челленджей
В production прокси-сервер может периодически отвечать 407 Proxy Authentication Required — например, при истечении сессии или балансировке нагрузки. OkHttp Authenticator автоматически повторяет запрос с обновлёнными credentials:
import okhttp3.Authenticator
import okhttp3.Credentials
import okhttp3.OkHttpClient
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
fun buildHardenedOkHttp(
username: String,
password: String
): OkHttpClient {
val proxyAuth = Authenticator { route: Route?, response ->
// Предотвращаем бесконечный цикл — максимум 2 попытки
if (response.request.header("Proxy-Authorization") != null) {
return@Authenticator null
}
val credential = Credentials.basic(username, password)
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
return OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator(proxyAuth)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
// Connection pool для переиспользования TCP-соединений
.connectionPool(okhttp3.ConnectionPool(5, 5, TimeUnit.MINUTES))
.build()
}
Ретраи с экспоненциальной задержкой
Для Ktor можно реализовать ретраи через перехватчик. Простой паттерн с экспоненциальной задержкой:
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.statement.*
import kotlinx.coroutines.delay
import kotlin.math.min
import kotlin.math.pow
suspend fun fetchWithRetry(
client: HttpClient,
url: String,
maxRetries: Int = 3,
baseDelayMs: Long = 500
): HttpResponse {
var attempt = 0
while (true) {
try {
val resp = client.get(url)
if (resp.status.value !in 500..599 && resp.status.value != 429) {
return resp
}
if (attempt >= maxRetries) return resp
} catch (e: Exception) {
if (attempt >= maxRetries) throw e
}
val delayMs = min(baseDelayMs * 2.0.pow(attempt).toLong(), 10_000)
delay(delayMs)
attempt++
}
}
TLS-конфигурация
Для OkHttp можно явно настроить TLS для совместимости с прокси, которые требуют определённые версии протокола:
import okhttp3.OkHttpClient
import java.net.InetSocketAddress
import java.net.Proxy
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import java.security.KeyStore
fun buildTlsClient(username: String, password: String): OkHttpClient {
val tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
tmf.init(null as KeyStore?)
val trustManagers = tmf.trustManagers
val x509Tm = trustManagers.first { it is X509TrustManager } as X509TrustManager
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, arrayOf(trustManagers), null)
return OkHttpClient.Builder()
.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress("gate.proxyhat.com", 8080)))
.proxyAuthenticator { _, response ->
response.request.newBuilder()
.header("Proxy-Authorization",
okhttp3.Credentials.basic(username, password))
.build()
}
.sslSocketFactory(sslContext.socketFactory, x509Tm)
.build()
}
Android NetworkSecurityConfig
На Android 7.0+ (API 24+) по умолчанию не доверяются пользовательские CA-сертификаты, а cleartext-трафик заблокирован. Если ваше приложение ходит через прокси, добавьте network_security_config.xml:
<!-- 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>
</domain-config>
</network-security-config>
И ссылку в AndroidManifest.xml:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
Это нужно только если прокси-соединение использует cleartext (HTTP CONNECT). Для HTTPS через прокси TLS-туннель устанавливается после CONNECT, и cleartext-флаг не требуется.
ProxyHat SDK и интеграция
ProxyHat предоставляет endpoint gate.proxyhat.com:8080 для HTTP и :1080 для SOCKS5. Параметры сессии и geo-таргетинга кодируются в username — это значит, что любой HTTP-клиент в Kotlin работает без специального SDK. Паттерн «флаги в username» повторяет подход, описанный в документации ProxyHat.
Для управления тарифом, ротацией IP и мониторингом используйте дашборд ProxyHat. Доступные локации и покрытие смотрите на странице локаций. Примеры использования для сбора данных — в разделе веб-скрапинг.
Этика и правовые аспекты
Kotlin веб-скрапинг с прокси — мощный инструмент, но с правовыми границами. Ключевые принципы:
- Публичные данные только. Не собирайте данные за логином, платным контентом или с нарушением CFAA в США.
- GDPR в ЕС. Персональные данные欧盟 граждан защищены GDPR. Сбор персональных данных без законного основания — нарушение.
- robots.txt. Уважайте директивы
robots.txt— это отраслевой стандарт, игнорирование которого может трактоваться как недобросовестное поведение. - Официальные API. Если у сервиса есть публичный API — используйте его. Это надёжнее, дешевле и законнее, чем скрапинг HTML.
- Rate limits. Не отправляйте запросы с частотой, которая деградирует сервис.
Semaphoreи задержки — не только техническая необходимость, но и этическая.
Key Takeaways
- Выбирайте движок по задаче: OkHttp — для максимального контроля и Android; Ktor CIO — для серверных и кроссплатформенных проектов.
- Прокси-аутентификация engine-specific: OkHttp использует
proxyAuthenticator, Ktor CIO требует ручнойProxy-AuthorizationвdefaultRequest. - Geo-таргетинг и sticky-сессии кодируются в username:
user-country-DE-city-berlin-session-abc123. - SOCKS5 на :1080 требует системных свойств
java.net.socks.username/password—小心 с глобальным состоянием в многопоточных приложениях. - Residential-прокси необходимы для социальных платформ и app-таргетов, блокирующих datacenter ASN.
- Конкурентность через Semaphore: 10–30 параллельных запросов — безопасный диапазон для residential-прокси.
- Production-харденинг: ретраи с экспоненциальной задержкой, connection pooling, TLS-конфигурация, обработка 407.
- Этика: публичные данные, robots.txt, официальные API, GDPR и CFAA.
FAQ
Что такое использование прокси в Kotlin?
Использование прокси в Kotlin — это настройка HTTP-клиентов (Ktor, OkHttp, java.net.HttpURLConnection) для маршрутизации трафика через промежуточный сервер. В Kotlin/JVM прокси настраивается через java.net.Proxy для транспортного уровня и заголовок Proxy-Authorization для аутентификации. Для residential-прокси параметры сессии и geo-таргетинга кодируются в username.
Зачем нужен прокси при разработке на Kotlin?
Прокси нужен для обхода IP-блокировок, geo-тестирования, сбора публичных данных и анонимизации трафика. Без прокси запросы идут с вашего серверного IP, который быстро попадает в blacklist антибот-систем. Residential-прокси маскируют запросы под реальных пользователей, повышая success rate с 10–20% (datacenter) до 90%+ (residential) для защищённых сайтов.
Какой тип прокси лучше для Kotlin-проектов?
Зависит от задачи. Datacenter-прокси — для API-мониторинга и QA, где блокировка по ASN не критична. Residential — для SERP-скрапинга, социальных платформ и e-commerce. Mobile — для максимальной доверенности (Instagram, TikTok). Для большинства production-скраперов на Kotlin оптимальны residential с ротацией IP и geo-таргетингом.
Как избежать блокировок при использовании прокси в Kotlin?
Используйте residential-прокси вместо datacenter, ограничивайте конкурентность через Semaphore (10–30 запросов), добавляйте задержки между запросами, ротируйте IP между запросами (без sticky-сессии для массового сбора), используйте реалистичный User-Agent, обрабатывайте 429 и 503 с экспоненциальной задержкой. Уважайте robots.txt и не превышайте разумный rate limit.
Можно ли использовать SOCKS5-прокси в Ktor?
Да, через Proxy(Proxy.Type.SOCKS, InetSocketAddress("gate.proxyhat.com", 1080)) в конфигурации engine. Аутентификация SOCKS5 в JVM настраивается через системные свойства java.net.socks.username и java.net.socks.password. В многопоточных приложениях с разными credentials это создаёт гонку — используйте OkHttp с кастомным Authenticator вместо глобальных свойств.






