Kotlin에서 프록시 사용하기: Ktor Client와 OkHttp 실전 가이드

Kotlin 백엔드 및 Android 개발자를 위한 프록시 통합 가이드. Ktor 3 CIO/OkHttp 엔진과 OkHttp를 활용한 ProxyHat 게이트웨이 연동, 지역 타겟팅, 스티키 세션, 동시성 제어, 프로덕션 하드닝까지 코드 중심으로 설명합니다.

Using Proxies in Kotlin: A Code-First Guide with Ktor and OkHttp
이 글의 목차

Kotlin에서 프록시를 사용하는 방법을 이해하는 것은 웹 스크래핑, API 모니터링, 자동화 도구를 구축하는 모든 개발자에게 필수적입니다. Kotlin proxy 설정은 Ktor Client의 유연한 엔진 시스템과 OkHttp의 검증된 네트워크 스택을 결합하면 강력한 동시성 제어와 함께 안정적인 HTTP 요청 파이프라인을 만들 수 있습니다. 이 가이드는 Ktor client proxy 구성부터 OkHttp proxy authentication 처리, 동시 Kotlin web scraping 패턴까지 코드 중심으로 다룹니다.

Kotlin에서 프록시를 사용하는 방법: 기본 개념

프록시는 클라이언트와 대상 서버 사이에 중계 서버를 두어 요청을 전달합니다. Kotlin 생태계에서 가장 널리 사용되는 두 HTTP 클라이언트는 Ktor ClientOkHttp입니다. 두 라이브러리 모두 프록시 설정을 지원하지만, 인증 처리 방식과 엔진별 동작이 다르므로 주의가 필요합니다.

ProxyHat은 gate.proxyhat.com:8080 게이트웨이를 통해 HTTP/HTTPS 프록시를 제공하며, 사용자명에 지역 타겟팅과 세션 정보를 인코딩하는 방식으로 동작합니다. 이는 RFC 7235 HTTP/1.1 Authentication의 Basic 인증 체계를 따릅니다.

프록시 유형 비교

프록시 유형 IP 소스 차단 위험 평균 지연 적합한 용도
데이터센터 호스팅 제공업체 ASN 높음 50–100ms API 호출, 비차단 대상
레지덴셜 실제 ISP 할당 IP 낮음 100–300ms 소셜 미디어, 이커머스, SERP
모바일 모바일 통신사 IP 매우 낮음 200–500ms 모바일 앱 타겟, 고도 보호된 사이트

프로젝트 설정: Ktor 3와 OkHttp 의존성

Kotlin 백엔드 프로젝트에서 Ktor 3.x와 OkHttp를 함께 사용하려면 Gradle 설정에 다음 의존성을 추가합니다. Ktor Client는 엔진을 선택할 수 있는 구조이므로, CIO(Coroutine-based I/O) 엔진과 OkHttp 엔진을 모두 포함할 수 있습니다.

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

dependencies {
    // Ktor Client 핵심 + CIO 엔진
    implementation("io.ktor:ktor-client-core:$ktorVersion")
    implementation("io.ktor:ktor-client-cio:$ktorVersion")
    // Ktor OkHttp 엔진 (프록시 인증 처리가 더 안정적)
    implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
    // Ktor 직렬화 및 로깅
    implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
    implementation("io.ktor:ktor-client-logging:$ktorVersion")

    // OkHttp (독립 사용 시)
    implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")

    // 코루틴
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

Android 프로젝트라면 ktor-client-android 엔진을 추가하고, 인터넷 권한과 usesCleartextTraffic 설정을 확인하세요. Android 9(API 28) 이상에서는 기본적으로 cleartext HTTP가 차단되므로, 프록시 게이트웨이가 HTTPS를 지원하는지 확인하거나 NetworkSecurityConfig에서 예외를 추가해야 합니다.

Ktor Client로 ProxyHat 게이트웨이 연동하기

Ktor Client에서 프록시를 설정하려면 엔진 구성에 proxyConfig를 전달합니다. 핵심 주의사항: Ktor의 CIO 엔진은 Proxy-Authorization 헤더를 자동으로 추가하지 않습니다. 따라서 defaultRequest 블록에서 수동으로 Basic 인증 헤더를 추가해야 합니다.

기본 HTTP 프록시 설정 (포트 8080)

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

fun buildProxyClient(
    proxyUser: String,
    proxyPass: String,
    country: String = "US",
    city: String? = null,
    sessionId: String? = null
): HttpClient {
    // 사용자명에 지역 및 세션 정보 인코딩
    val fullUser = buildString {
        append("user-country-$country")
        if (city != null) append("-city-$city")
        if (sessionId != null) append("-session-$sessionId")
    }

    // Proxy-Authorization Basic 헤더 값 생성
    val credentials = "$fullUser:$proxyPass"
    val basicAuth = "Basic " + Base64.getEncoder()
        .encodeToString(credentials.toByteArray())

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

        defaultRequest {
            // CIO 엔진은 Proxy-Authorization을 자동 전송하지 않으므로
            // 수동으로 헤더에 추가해야 함
            header("Proxy-Authorization", basicAuth)
            header(HttpHeaders.UserAgent,
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
        }

        // 타임아웃 설정
        install(HttpTimeout) {
            connectTimeoutMillis = 10_000
            requestTimeoutMillis = 30_000
            socketTimeoutMillis = 30_000
        }
    }
}

suspend fun main() {
    val client = buildProxyClient(
        proxyUser = "myuser",
        proxyPass = "mypass",
        country = "DE",
        city = "berlin",
        sessionId = "abc123"
    )

    client.use { c ->
        val response: HttpResponse = c.get("https://httpbin.org/ip")
        println("Status: ${response.status}")
        println("Body: ${response.bodyAsText()}")
        // 출력 예: {"origin": "85.214.x.x"} — 독일 베를린 레지덴셜 IP
    }
}

OkHttp 엔진으로 전환하면 Proxy-Authorization이 자동 처리된다

Ktor Client의 OkHttp 엔진을 사용하면, 엔진이 내부적으로 Authenticator를 통해 407 Proxy Authentication Required 챌린지에 응답합니다. 이 경우 defaultRequest에 수동으로 Proxy-Authorization 헤더를 추가할 필요가 없습니다.

import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import okhttp3.Authenticator
import okhttp3.Credentials
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy

fun buildOkHttpProxyClient(
    proxyUser: String,
    proxyPass: String,
    country: String = "US",
    sessionId: String? = null
): HttpClient {
    val fullUser = buildString {
        append("user-country-$country")
        if (sessionId != null) append("-session-$sessionId")
    }

    // OkHttp Authenticator — 407 챌린지에 자동 응답
    val proxyAuth = Authenticator { _: Route?, response ->
        val credential = Credentials.basic(fullUser, proxyPass)
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    }

    return HttpClient(OkHttp) {
        engine {
            config {
                proxy(Proxy(Proxy.Type.HTTP,
                    InetSocketAddress("gate.proxyhat.com", 8080)))
                proxyAuthenticator(proxyAuth)
                // 연결 풀 설정
                connectionPool(
                    okhttp3.ConnectionPool(
                        maxIdleConnections = 20,
                        keepAliveDuration = 5,
                        java.util.concurrent.TimeUnit.MINUTES
                    )
                )
                // 재시도 설정
                retryOnConnectionFailure(true)
            }
        }

        install(HttpTimeout) {
            connectTimeoutMillis = 10_000
            requestTimeoutMillis = 30_000
            socketTimeoutMillis = 30_000
        }
    }
}

suspend fun main() {
    val client = buildOkHttpProxyClient(
        proxyUser = "myuser",
        proxyPass = "mypass",
        country = "US",
        sessionId = "order-001"
    )

    client.use { c ->
        val resp = c.get("https://httpbin.org/headers")
        println(resp.bodyAsText())
    }
}

OkHttp 직접 사용: 기본 프록시 인증

Ktor를 거치지 않고 OkHttp를 직접 사용하는 경우도 많습니다. Android 앱이나 레거시 Kotlin 백엔드에서 특히 흔합니다. OkHttp는 OkHttpClient.Builder().proxy()로 프록시 주소를 지정하고, proxyAuthenticator로 407 챌린지에 대응합니다.

import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit

fun buildOkHttpClient(
    proxyUser: String,
    proxyPass: String,
    country: String = "US",
    city: String? = null,
    sessionId: String? = null
): OkHttpClient {
    val fullUser = buildString {
        append("user-country-$country")
        if (city != null) append("-city-$city")
        if (sessionId != null) append("-session-$sessionId")
    }

    val proxyAuth = Authenticator { _: Route?, response ->
        val credential = Credentials.basic(fullUser, proxyPass)
        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(30, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(ConnectionPool(20, 5, TimeUnit.MINUTES))
        .build()
}

fun main() {
    val client = buildOkHttpClient(
        proxyUser = "myuser",
        proxyPass = "mypass",
        country = "JP",
        city = "tokyo",
        sessionId = "sess-001"
    )

    val request = Request.Builder()
        .url("https://httpbin.org/ip")
        .header("User-Agent",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
        .build()

    client.newCall(request).execute().use { response ->
        println("Status: ${response.code}")
        println("Body: ${response.body?.string()}")
        // 출력 예: {"origin": "126.74.x.x"} — 일본 도쿄 레지덴셜 IP
    }
}

SOCKS5 프록시: 포트 1080과 시스템 속성

ProxyHat은 gate.proxyhat.com:1080에서 SOCKS5 프록시도 지원합니다. JVM에서 SOCKS5 프록시 인증을 사용하려면 java.net.socks.usernamejava.net.socks.password 시스템 속성을 설정하거나, OkHttp의 Authenticator를 사용할 수 있습니다. Ktor CIO 엔진은 SOCKS5를 직접 지원하지 않으므로 OkHttp 엔진을 사용해야 합니다.

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

fun buildSocks5Client(
    proxyUser: String,
    proxyPass: String,
    country: String = "US",
    sessionId: String? = null
): OkHttpClient {
    // SOCKS5 인증을 위한 시스템 속성 설정
    val fullUser = buildString {
        append("user-country-$country")
        if (sessionId != null) append("-session-$sessionId")
    }

    System.setProperty("java.net.socks.username", fullUser)
    System.setProperty("java.net.socks.password", proxyPass)

    // SOCKS5용 Authenticator (일부 JVM 구현에서 필요)
    val proxyAuth = Authenticator { _: Route?, response ->
        val credential = Credentials.basic(fullUser, proxyPass)
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    }

    return OkHttpClient.Builder()
        .proxy(Proxy(Proxy.Type.SOCKS,
            InetSocketAddress("gate.proxyhat.com", 1080)))
        .proxyAuthenticator(proxyAuth)
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build()
}

fun main() {
    val client = buildSocks5Client(
        proxyUser = "myuser",
        proxyPass = "mypass",
        country = "GB",
        sessionId = "uk-001"
    )

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

    client.newCall(request).execute().use { response ->
        println("SOCKS5 Status: ${response.code}")
        println("SOCKS5 Body: ${response.body?.string()}")
    }
}

레지덴셜 프록시가 필요한 이유: 데이터센터 ASN 차단

소셜 미디어 플랫폼(Instagram, Twitter/X, TikTok)과 많은 이커머스 사이트는 데이터센터 IP 대역을 ASN 데이터베이스와 대조하여 차단합니다. Autonomous System(AS) 번호를 기준으로 AWS, Google Cloud, DigitalOcean 등의 IP를 필터링하는 방식입니다. 이 경우 데이터센터 프록시로는 403 Forbidden이나 CAPTCHA 챌린지를 받게 됩니다.

레지덴셜 프록시는 실제 ISP 가입자에게 할당된 IP를 사용하므로, 대상 서버는 일반 사용자의 요청과 구분할 수 없습니다. ProxyHat의 레지덴셜 프록시 풀은 190개 이상 국가를 지원하며, 전체 위치 목록에서 확인할 수 있습니다.

코루틴 동시 요청 팬아웃: async/awaitAll + Semaphore

Kotlin 코루틴을 사용하면 대량의 프록시 요청을 동시에 실행하면서 동시성을 제어할 수 있습니다. async로 각 요청을 시작하고, Semaphore로 동시 실행 수를 제한하며, awaitAll로 모든 결과를 수집하는 패턴이 효율적입니다.

import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
import java.util.Base64
import java.util.concurrent.atomic.AtomicInteger

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

suspend fun fanOutScrape(
    urls: List<String>,
    proxyUser: String,
    proxyPass: String,
    maxConcurrency: Int = 10
): List<ScrapeResult> {
    val semaphore = Semaphore(maxConcurrency)
    val successCount = AtomicInteger(0)
    val failCount = AtomicInteger(0)

    // 각 요청마다 다른 세션 ID로 IP 로테이션 유도
    val client = HttpClient(OkHttp) {
        install(HttpTimeout) {
            connectTimeoutMillis = 10_000
            requestTimeoutMillis = 30_000
            socketTimeoutMillis = 30_000
        }
    }

    val results = coroutineScope {
        urls.mapIndexed { index, url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    val sessionId = "rot-${System.currentTimeMillis()}-$index"
                    val fullUser = "user-country-US-session-$sessionId"
                    val basicAuth = "Basic " + Base64.getEncoder()
                        .encodeToString("$fullUser:$proxyPass".toByteArray())

                    val start = System.currentTimeMillis()
                    try {
                        val response = client.get(url) {
                            header("Proxy-Authorization", basicAuth)
                            header("User-Agent",
                                "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
                        }
                        val duration = System.currentTimeMillis() - start
                        successCount.incrementAndGet()
                        ScrapeResult(url, response.status.value,
                            response.bodyAsText(), duration)
                    } catch (e: Exception) {
                        val duration = System.currentTimeMillis() - start
                        failCount.incrementAndGet()
                        ScrapeResult(url, -1, e.message ?: "error", duration)
                    }
                }
            }
        }.awaitAll()
    }

    client.close()
    println("성공: ${successCount.get()}, 실패: ${failCount.get()}")
    return results
}

suspend fun main() {
    val urls = (1..50).map { i ->
        "https://httpbin.org/anything?page=$i"
    }

    val results = fanOutScrape(
        urls = urls,
        proxyUser = "myuser",
        proxyPass = "mypass",
        maxConcurrency = 10
    )

    results.take(5).forEach { r ->
        println("[${r.status}] ${r.url} (${r.durationMs}ms)")
    }
    // 출력 예:
    // 성공: 50, 실패: 0
    // [200] https://httpbin.org/anything?page=1 (234ms)
    // [200] https://httpbin.org/anything?page=2 (189ms)
    // ...
}

주의: 위 예제는 OkHttp 엔진을 사용하지만 프록시 주소를 엔진에 설정하지 않고 대신 Proxy-Authorization 헤더만 추가하는 방식은 CIO 엔진에서만 동작합니다. OkHttp 엔진을 사용할 때는 반드시 engine { config { proxy(...) } } 블록에서 프록시 주소를 설정하세요. 아래는 OkHttp 엔진에 프록시를 올바르게 설정한 버전입니다.

import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
import okhttp3.Authenticator
import okhttp3.Credentials
import java.net.InetSocketAddress
import java.net.Proxy

suspend fun fanOutWithOkHttpEngine(
    urls: List<String>,
    proxyUser: String,
    proxyPass: String,
    maxConcurrency: Int = 10
): List<Pair<String, Int>> {
    val semaphore = Semaphore(maxConcurrency)

    val client = HttpClient(OkHttp) {
        engine {
            config {
                proxy(Proxy(Proxy.Type.HTTP,
                    InetSocketAddress("gate.proxyhat.com", 8080)))
                proxyAuthenticator(Authenticator { _, response ->
                    // 각 요청마다 다른 세션으로 IP 로테이션
                    val sessionId = "rot-${System.currentTimeMillis()}"
                    val fullUser = "user-country-US-session-$sessionId"
                    val credential = Credentials.basic(fullUser, proxyPass)
                    response.request.newBuilder()
                        .header("Proxy-Authorization", credential)
                        .build()
                })
            }
        }
        install(HttpTimeout) {
            connectTimeoutMillis = 10_000
            requestTimeoutMillis = 30_000
            socketTimeoutMillis = 30_000
        }
    }

    val results = coroutineScope {
        urls.map { url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    try {
                        val resp = client.get(url)
                        url to resp.status.value
                    } catch (e: Exception) {
                        url to -1
                    }
                }
            }
        }.awaitAll()
    }

    client.close()
    return results
}

suspend fun main() {
    val urls = (1..20).map { i ->
        "https://httpbin.org/ip?page=$i"
    }

    val results = fanOutWithOkHttpEngine(
        urls = urls,
        proxyUser = "myuser",
        proxyPass = "mypass",
        maxConcurrency = 5
    )

    results.forEach { (url, status) ->
        println("[$status] $url")
    }
}

프로덕션 하드닝: 재시도, 타임아웃, TLS, Android 보안

OkHttp Authenticator로 407 챌린지 처리

프록시 서버가 407 Proxy Authentication Required를 반환하면, OkHttp의 Authenticator 인터페이스가 자동으로 인증 헤더를 추가하여 재시도합니다. 이 메커니즘은 RFC 7235 Section 4.1에 정의된 Proxy-Authenticate/Proxy-Authorization 흐름을 따릅니다. 재시도 횟수는 OkHttp가 20회로 제한하므로 무한 루프 위험은 없습니다.

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

class ResilientProxyClient(
    private val proxyUser: String,
    private val proxyPass: String,
    private val country: String = "US"
) {
    private val fullUser = "user-country-$country"

    private val client: OkHttpClient = OkHttpClient.Builder()
        .proxy(Proxy(Proxy.Type.HTTP,
            InetSocketAddress("gate.proxyhat.com", 8080)))
        .proxyAuthenticator { _: Route?, response ->
            // 407 챌린지 수신 시 인증 헤더 추가 후 재시도
            val credential = Credentials.basic(fullUser, proxyPass)
            response.request.newBuilder()
                .header("Proxy-Authorization", credential)
                .build()
        }
        // 일반 401/407이 아닌 인증 실패 시 추가 재시도 방지
        .authenticator { _: Route?, response ->
            if (response.request.header("Authorization") != null) {
                null // 이미 인증 헤더가 있으면 재시도 중단
            } else {
                response.request.newBuilder()
                    .header("Authorization",
                        Credentials.basic(fullUser, proxyPass))
                    .build()
            }
        }
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .retryOnConnectionFailure(true)
        .connectionPool(ConnectionPool(20, 5, TimeUnit.MINUTES))
        .build()

    fun executeWithRetry(
        request: Request,
        maxRetries: Int = 3
    ): Response {
        var lastException: IOException? = null
        for (attempt in 1..maxRetries) {
            try {
                // execute()는 동기 호출 — 코루틴에서는 withContext(Dispatchers.IO) 사용
                val response = client.newCall(request).execute()
                if (response.isSuccessful || response.code in 400..499) {
                    return response
                }
                response.close()
            } catch (e: IOException) {
                lastException = e
                println("시도 $attempt 실패: ${e.message}")
                Thread.sleep(500L * attempt) // 지수 백오프
            }
        }
        throw lastException ?: IOException("최대 재시도 횟수 초과")
    }
}

fun main() {
    val rpc = ResilientProxyClient(
        proxyUser = "myuser",
        proxyPass = "mypass",
        country = "FR"
    )

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

    rpc.executeWithRetry(request).use { response ->
        println("최종 상태: ${response.code}")
    }
}

TLS/SSL 구성

프록시와 대상 서버 간의 TLS 연결은 OkHttp가 기본적으로 검증합니다. 자체 서명 인증서나 MITM 프록시를 사용하는 환경이 아니라면 기본 설정을 유지하세요. 필요한 경우 ConnectionSpec으로 허용할 TLS 버전을 제한할 수 있습니다.

import okhttp3.*
import java.util.Arrays
import java.util.concurrent.TimeUnit

fun buildTlsClient(): OkHttpClient {
    val modernTls = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
        .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3)
        .cipherSuites(
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_AES_256_GCM_SHA384,
            CipherSuite.TLS_CHACHA20_POLY1305_SHA256
        )
        .build()

    return OkHttpClient.Builder()
        .connectionSpecs(Arrays.asList(modernTls, ConnectionSpec.CLEARTEXT))
        .connectTimeout(10, TimeUnit.SECONDS)
        .build()
}

Android NetworkSecurityConfig

Android 9(API 28) 이상에서 cleartext HTTP 트래픽이 기본 차단됩니다. ProxyHat 게이트웨이에 HTTP로 연결하려면 res/xml/network_security_config.xml에 예외를 추가해야 합니다. 단, 실제 대상 사이트와의 통신은 HTTPS로 이루어지므로 게이트웨이 연결만 예외 처리하면 됩니다.

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">gate.proxyhat.com</domain>
    </domain-config>
</network-security-config>

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

ProxyHat SDK와 패턴 매칭

ProxyHat SDK는 위에서 설명한 패턴(엔진별 프록시 설정, 사용자명 인코딩, 407 인증 처리)을 내부적으로 구현합니다. SDK를 사용하면 인증 헤더 관리와 세션 로테이션을 직접 처리할 필요가 없습니다. 자세한 내용은 ProxyHat 공식 문서를 참조하세요.

가격 정보와 플랜 비교는 프라이싱 페이지에서 확인할 수 있으며, 웹 스크래핑과 SERP 트래킹 사용 사례는 각각 웹 스크래핑SERP 트래킹 페이지에서 자세히 다룹니다.

윤리적 스크래핑: 법적 고려사항

프록시를 사용한 데이터 수집은 강력한 도구이지만, 법적 및 윤리적 경계를 존중해야 합니다.

  • 공개 데이터만 수집: 로그인이 필요한 페이지나 결제 게이트웨이 뒤의 데이터는 수집하지 마세요. 공개적으로 접근 가능한 페이지만 대상으로 합니다.
  • robots.txt 준수: 대상 사이트의 robots.txt를 확인하고 금지된 경로는 스크래핑하지 않습니다. 이는 robots.txt 프로토콜에 따른 권장 사항입니다.
  • CFAA (미국): 미국 컴퓨터 사기 및 남용법(CFAA)은 무단 접근을 금지합니다. 공개 페이지 스크래핑은 일반적으로 허용되지만, 서비스 약관 위반과 결합되면 법적 위험이 발생할 수 있습니다.
  • GDPR (EU): 개인 데이터를 수집하는 경우 EU 일반 데이터 보호 규칙(GDPR)이 적용될 수 있습니다. IP 주소도 개인 데이터로 간주되므로 주의가 필요합니다.
  • 공식 API 우선: 대상 플랫폼이 공식 API를 제공한다면, 스크래핑보다 API를 사용하는 것이 더 안정적이고 법적으로 안전합니다. API 호출 시에도 프록시로 지역별 데이터를 수집할 수 있습니다.

핵심 요약: Kotlin 프록시 사용 가이드

Key Takeaways:

  • Ktor CIO 엔진은 Proxy-Authorization 헤더를 자동 전송하지 않으므로 defaultRequest에서 수동 추가해야 합니다.
  • Ktor OkHttp 엔진과 OkHttp 직접 사용 시 Authenticator가 407 챌린지에 자동 응답합니다.
  • ProxyHat 사용자명에 user-country-DE-city-berlin-session-abc123 형식으로 지역과 세션을 인코딩합니다.
  • SOCKS5는 포트 1080을 사용하며, java.net.socks.username/password 시스템 속성으로 인증합니다.
  • 코루틴 async/awaitAll + Semaphore로 동시성을 제어하면서 대량 요청을 실행합니다.
  • 소셜 미디어와 모바일 앱 타겟은 데이터센터 ASN 차단을 우회하기 위해 레지덴셜 프록시가 필수입니다.
  • Android에서는 NetworkSecurityConfiggate.proxyhat.com의 cleartext 트래픽을 허용해야 합니다.
  • 공개 데이터만 수집하고, robots.txt를 준수하며, 공식 API를 우선 사용하세요.

FAQ

Kotlin에서 프록시를 사용하는 방법이란 무엇인가요?

Kotlin에서 프록시를 사용하는 방법은 HTTP 클라이언트(Ktor Client 또는 OkHttp)에 프록시 서버 주소와 인증 정보를 설정하여 요청을 중계하는 것입니다. Ktor의 CIO/OkHttp 엔진에서는 ProxyConfig를 통해 프록시를 지정하고, Proxy-Authorization 헤더로 인증합니다. OkHttp에서는 OkHttpClient.Builder().proxy() 메서드와 Authenticator 인터페이스를 사용해 407 챌린지에 대응합니다.

Kotlin에서 프록시를 사용할 때 왜 중요한가요?

프록시는 IP 기반 차단을 우회하고, 지역별 콘텐츠에 접근하며, 대량 요청 시 속도 제한을 분산시키는 데 필수적입니다. 특히 데이터센터 ASN이 차단되는 소셜 미디어나 모바일 앱 타겟에서는 레지덴셜 프록시가 필요합니다. Kotlin의 코루틴과 결합하면 동시성 제어와 함께 안정적인 웹 스크래핑 파이프라인을 구축할 수 있습니다.

Kotlin 프록시 사용에 가장 적합한 프록시 유형은 무엇인가요?

대상 사이트가 데이터센터 IP를 차단하는 경우 레지덴셜 프록시가 가장 적합합니다. 레지덴셜 프록시는 실제 ISP에서 발급된 IP를 사용하므로 일반 사용자 트래픽과 구분하기 어렵습니다. 모바일 앱 타겟이라면 모바일 프록시를 고려하고, 단순 API 호출이나 차단이 없는 대상이라면 데이터센터 프록시로 비용을 절감할 수 있습니다.

Kotlin에서 프록시를 사용할 때 차단을 피하려면 어떻게 하나요?

요청당 IP 로테이션을 사용하고, 스티키 세션으로 세션 일관성을 유지하며, Semaphore로 동시 요청 수를 제한하세요. User-Agent와 Accept 헤더를 실제 브라우저에 맞추고, 요청 간격에 지터(jitter)를 추가하세요. robots.txt를 준수하고, 공개 데이터만 수집하며, 가능하면 공식 API를 우선 사용하는 것이 윤리적이고 지속 가능한 접근입니다.

ProxyHat 게이트웨이에 연결할 때 기본 포트는 무엇인가요?

ProxyHat HTTP 프록시는 gate.proxyhat.com:8080을 사용하고, SOCKS5 프록시는 gate.proxyhat.com:1080을 사용합니다. Ktor OkHttp 엔진과 OkHttp 직접 사용 모두 동일한 주소와 포트를 사용합니다. 인증 정보는 사용자명에 user-country-US-session-abc123 형식으로 인코딩됩니다.

자주 묻는 질문

Kotlin에서 프록시를 사용하는 방법이란 무엇인가요?

Kotlin에서 프록시를 사용하는 방법은 HTTP 클라이언트(Ktor Client 또는 OkHttp)에 프록시 서버 주소와 인증 정보를 설정하여 요청을 중계하는 것입니다. Ktor의 CIO/OkHttp 엔진에서는 ProxyConfig를 통해 프록시를 지정하고, Proxy-Authorization 헤더로 인증합니다. OkHttp에서는 OkHttpClient.Builder().proxy() 메서드와 Authenticator 인터페이스를 사용해 407 챌린지에 대응합니다.

Kotlin에서 프록시를 사용할 때 왜 중요한가요?

프록시는 IP 기반 차단을 우회하고, 지역별 콘텐츠에 접근하며, 대량 요청 시 속도 제한을 분산시키는 데 필수적입니다. 특히 데이터센터 ASN이 차단되는 소셜 미디어나 모바일 앱 타겟에서는 레지덴셜 프록시가 필요합니다. Kotlin의 코루틴과 결합하면 동시성 제어와 함께 안정적인 웹 스크래핑 파이프라인을 구축할 수 있습니다.

Kotlin 프록시 사용에 가장 적합한 프록시 유형은 무엇인가요?

대상 사이트가 데이터센터 IP를 차단하는 경우 레지덴셜 프록시가 가장 적합합니다. 레지덴셜 프록시는 실제 ISP에서 발급된 IP를 사용하므로 일반 사용자 트래픽과 구분하기 어렵습니다. 모바일 앱 타겟이라면 모바일 프록시를 고려하고, 단순 API 호출이나 차단이 없는 대상이라면 데이터센터 프록시로 비용을 절감할 수 있습니다.

Kotlin에서 프록시를 사용할 때 차단을 피하려면 어떻게 하나요?

요청당 IP 로테이션을 사용하고, 스티키 세션으로 세션 일관성을 유지하며, Semaphore로 동시 요청 수를 제한하세요. User-Agent와 Accept 헤더를 실제 브라우저에 맞추고, 요청 간격에 지터(jitter)를 추가하세요. robots.txt를 준수하고, 공개 데이터만 수집하며, 가능하면 공식 API를 우선 사용하는 것이 윤리적이고 지속 가능한 접근입니다.

시작할 준비가 되셨나요?

148개국 이상의 주거용, ISP, 모바일 프록시. 무료 계정을 만드세요.

무료 계정 만들기
← 블로그로 돌아가기