Kotlinでのプロキシ利用:Ktor ClientとOkHttpによる実装ガイド

Kotlin開発者向けに、Ktor 3 HttpClientとOkHttpでプロキシを設定する方法を解説。geo-targeting、sticky session、SOCKS5、コルーチン並行制御、本番運用のベストプラクティスまで網羅した実装ガイド。

Using Proxies in Kotlin: A Code-First Guide with Ktor and OkHttp
この記事の内容

Kotlinでのプロキシ利用は、Androidアプリからバックエンドスクレイピングまで幅広い場面で必要になります。本記事では Ktor ClientOkHttp の2つのHTTPクライアントでProxyHatのプロキシを経由する方法を、実行可能なコードとともに解説します。

Kotlinでのプロキシ利用:なぜ必要なのか

データセンタープロキシは高速ですが、多くのウェブサイトやモバイルAPIがホスティングプロバイダーのASNをブロックします。MDNのプロキシ解説にあるように、プロキシはクライアントとサーバー間の中継点として機能しますが、IPの性質(レジデンシャルかデータセンターか)が成否を分けます。

レジデンシャルプロキシは実際のISPに割り当てられたIPを使用するため、ターゲットサイトのIPレピューションチェックを通過しやすくなります。SNSプラットフォームやe-commerceサイト、SERP収集などで Kotlinウェブスクレイピング を行う場合、レジデンシャルプロキシはほぼ必須です。

プロジェクトセットアップ

Gradle依存関係

Ktor 3.xとOkHttpを併用する場合、以下の依存関係を build.gradle.kts に追加します。

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.0.21"
    application
}

dependencies {
    // Ktor Client 3.x with CIO engine
    implementation("io.ktor:ktor-client-core:3.0.3")
    implementation("io.ktor:ktor-client-cio:3.0.3")
    implementation("io.ktor:ktor-client-auth:3.0.3")

    // OkHttp for baseline / comparison
    implementation("com.squareup.okhttp3:okhttp:4.12.0")

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
}

OkHttpベースライン:java.net.Proxy

まずはOkHttpで直接プロキシを設定する基本パターンです。OkHttpClient.Builder().proxy()java.net.Proxy を渡します。

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

val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080
val username = "user-country-DE-city-berlin"
val password = "pass"

// Proxy-Authorization用のAuthenticator設定
val proxyAuth = object : Authenticator() {
    override fun getPasswordAuthentication(): PasswordAuthentication {
        return PasswordAuthentication(username, password.toCharArray())
    }
}

val client = OkHttpClient.Builder()
    .proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort)))
    .authenticator(proxyAuth)
    .connectTimeout(10_000, java.util.concurrent.TimeUnit.MILLISECONDS)
    .readTimeout(30_000, java.util.concurrent.TimeUnit.MILLISECONDS)
    .build()

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

client.newCall(request).execute().use { response ->
    println(response.body?.string())
    // => {"origin": "xxx.xxx.xxx.xxx"} (ドイツ・ベルリンのIP)
}

ProxyHatゲートウェイへのルーティング

geo-targetingとsticky sessionのエンコード

ProxyHatでは、ユーザー名にgeo-targetingとsticky sessionをエンコードします。これにより追加のヘッダーやクエリパラメータなしで柔軟な制御が可能です。

パラメータ 形式
国指定 user-country-{ISO} user-country-US
都市指定 user-country-DE-city-berlin ベルリンのIP
Sticky session -session-{id} user-session-abc123
組み合わせ user-country-DE-city-berlin-session-abc123 ベルリン固定IP

Ktor Clientでのプロキシ認証

Ktorではプロキシ認証の実装がエンジン固有です。CIOエンジンの場合、defaultRequestProxy-Authorization: Basic ヘッダーを明示的に追加する必要があります。詳細は Ktor公式ドキュメント を参照してください。

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 io.ktor.http.*
import java.util.Base64

val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080
val username = "user-country-DE-city-berlin"
val password = "pass"

// Basic認証ヘッダーを生成
val credentials = "$username:$password"
val encoded = Base64.getEncoder().encodeToString(credentials.toByteArray())

val ktorClient = HttpClient(CIO) {
    engine {
        proxy = ProxyBuilder.http("http://$proxyHost:$proxyPort")
    }
    defaultRequest {
        header("Proxy-Authorization", "Basic $encoded")
    }
}

// リクエスト実行
val response: HttpResponse = ktorClient.get("https://httpbin.org/ip")
println(response.bodyAsText())
// => {"origin": "xxx.xxx.xxx.xxx"} (ベルリンIP)

ktorClient.close()

注意: KtorのCIOエンジンは Proxy-Authorization ヘッダーを自動付与しないため、defaultRequest で明示的に設定する必要があります。OkHttpエンジンを使用する場合は Authenticator パターンが使えます。

SOCKS5プロキシの利用(ポート1080)

SOCKS5を使用する場合、JVMのシステムプロパティで認証情報を設定するのが最もシンプルです。RFC 1928 で規定されたSOCKS5プロトコルは、HTTPプロキシとは異なるトンネリング方式を使用します。

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

// SOCKS5認証用システムプロパティ
System.setProperty("java.net.socks.username", "user-country-US")
System.setProperty("java.net.socks.password", "pass")

val socksClient = HttpClient(CIO) {
    engine {
        proxy = ProxyBuilder.socks("gate.proxyhat.com", 1080)
    }
}

val resp: HttpResponse = socksClient.get("https://httpbin.org/ip")
println(resp.bodyAsText())
socksClient.close()

OkHttpでSOCKS5を使用する場合は Proxy(Proxy.Type.SOCKS, ...) を指定し、Authenticator で認証情報を返します。SOCKS5プロキシはHTTPSトラフィックをより透過的にトンネリングするため、一部の厳格なファイアウォール環境で有利です。

レジデンシャルプロキシで並行スクレイピング

SNSやモバイルAPIはデータセンターアドレス(AWS、GCP、AzureのASN)をブロックすることが多く、レジデンシャルプロキシが必須になります。以下はKotlinコルーチンで async/awaitAll を使い、Semaphore で同時接続数を制御しながら複数URLを並行取得する例です。

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 io.ktor.http.*
import kotlinx.coroutines.*
import java.util.Base64

val proxyHost = "gate.proxyhat.com"
val proxyPort = 8080

fun buildClient(countryCode: String): HttpClient {
    val username = "user-country-$countryCode-session-${java.util.UUID.randomUUID()}"
    val encoded = Base64.getEncoder()
        .encodeToString("$username:pass".toByteArray())

    return HttpClient(CIO) {
        engine {
            proxy = ProxyBuilder.http("http://$proxyHost:$proxyPort")
        }
        defaultRequest {
            header("Proxy-Authorization", "Basic $encoded")
            header(HttpHeaders.UserAgent, "Mozilla/5.0 (compatible; ScraperBot/1.0)")
        }
    }
}

suspend fun fetchAll(urls: List<String>, concurrency: Int = 10): List<String> {
    val semaphore = Semaphore(concurrency)

    return coroutineScope {
        urls.map { url ->
            async(Dispatchers.IO) {
                semaphore.withPermit {
                    val client = buildClient("US")
                    try {
                        val resp = client.get(url)
                        if (resp.status == HttpStatusCode.OK) {
                            resp.bodyAsText()
                        } else {
                            "ERROR ${resp.status.value}"
                        }
                    } catch (e: Exception) {
                        "FAIL: ${e.message}"
                    } finally {
                        client.close()
                    }
                }
            }
        }.awaitAll()
    }
}

// 使用例
fun main() = runBlocking {
    val urls = (1..50).map { "https://httpbin.org/delay/1?id=$it" }
    val results = fetchAll(urls, concurrency = 10)
    results.forEachIndexed { i, r ->
        println("[$i] ${r.take(80)}")
    }
}

このパターンでは、各コルーチンが独立したsticky sessionを持つクライアントを作成し、Semaphore(10) で同時接続を10に制限しています。これによりターゲットサーバーへの負荷を抑えつつ、約5倍のスループット向上が期待できます(直列実行と比較して)。

本番運用のためのハードニング

OkHttp Authenticatorで407チャレンジに対応

プロキシが 407 Proxy Authentication Required を返した場合、OkHttpの Authenticator インターフェースで自動的に認証ヘッダーを再付与してリトライできます。

import okhttp3.Authenticator
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.Base64
import java.util.concurrent.TimeUnit

val proxyAuthenticator = Authenticator { _: Route?, response: Response ->
    if (response.code == 407) {
        val credential = Credentials.basic("user-country-US", "pass")
        response.request.newBuilder()
            .header("Proxy-Authorization", credential)
            .build()
    } else {
        null // 他のステータスコードはリトライしない
    }
}

val productionClient = 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(okhttp3.ConnectionPool(20, 5, TimeUnit.MINUTES))
    .build()

タイムアウトとリトライのベストプラクティス

  • 接続タイムアウト: 10秒〜15秒。プロキシ経由のDNS解決とTCPハンドシェイクには直結より時間がかかります。
  • 読み取りタイムアウト: 30秒。大容量HTMLやAPIレスポンスを考慮。
  • リトライ: 指数バックオフで3回まで。Ktorでは HttpRetry プラグインが利用可能です。
  • 接続プール: 同一プロキシへの接続を再利用することで、ハンドシェイクオーバーヘッドを削減。OkHttpでは ConnectionPool(20, 5, TimeUnit.MINUTES) で最大20接続を5分間キープします。

TLS/SSL設定

プロキシ経由のHTTPSでは、プロキシ自体はTLSを終端せずCONNECTメソッドでトンネルを確立します。Ktor/OkHttpはデフォルトでJVMのトラストストアを使用しますが、カスタムCA証明書が必要な場合は以下のように設定します。

// Ktor 3でカスタムTLSを設定する例
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import java.security.KeyStore
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory

val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(KeyStore.getDefaultInstance())

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null)

val tlsClient = HttpClient(CIO) {
    engine {
        https {
            trustManager = tmf.trustManagers[0] as javax.net.ssl.X509TrustManager
        }
        proxy = ProxyBuilder.http("http://gate.proxyhat.com:8080")
    }
}

AndroidのNetworkSecurityConfig

Android 9(API 28)以降では、デフォルトでcleartext HTTPトラフィックがブロックされます。プロキシの認証エンドポイントがHTTPの場合は network_security_config.xml で例外を設定する必要があります。ただし、プロキシ自体はHTTPS CONNECTトンネリングで通信するため、ターゲットURLがHTTPSであれば通常は問題ありません。詳細は Android Network Security Configuration を参照してください。

倫理的スクレイピングと法務

プロキシを使ったスクレイピングは強力ですが、法的・倫理的ガイドラインを遵守することが不可欠です。

  • 公開データのみ: ログイン背後のデータやパスワードで保護されたコンテンツは避ける。
  • 米国CFAA: Computer Fraud and Abuse Actは、不正アクセスを禁じます。ToS違反がCFAA違反に直結するかは判例により異なりますが、慎重なアプローチが重要です。
  • EU GDPR: 個人データの収集・処理には同意ベースまたは合法的根拠が必要です。IPアドレスも個人データとみなされます。
  • 公式APIを優先: 多くのプラットフォームが公式APIを提供しており、利用可能な場合はスクレイピングより優先すべきです。
  • robots.txtの尊重: 標準的なクローラー礼儀として robots.txt を確認し、許可されたパスのみにアクセスする。

ProxyHat SDKは、上記のパターンをラップし、セッション管理やローテーションを抽象化します。ProxyHat公式ドキュメント でSDKの詳細を確認できます。また、プロキシプランの料金利用可能なロケーションについても参照してください。

プロキシタイプの比較

プロキシタイプ IPソース 検知リスク 適した用途
レジデンシャル ISP割当IP SNS、e-commerce、SERP収集
モバイル 携帯キャリアIP 極低 モバイルAPI、アプリテスト
データセンター ホスティングプロバイダー 高速取得、制限の緩いサイト

ウェブスクレイピングのユースケースSERPトラッキングのページで、具体的な活用シナリオを確認できます。

Key Takeaways(重要ポイント)

  • Ktor CIOエンジンでは Proxy-Authorization: Basic ヘッダーを defaultRequest で明示的に設定する必要がある。
  • OkHttpでは Authenticator で407チャレンジに対応し、自動リトライを実装できる。
  • geo-targetingとsticky sessionはユーザー名にエンコードする(例: user-country-DE-city-berlin-session-abc123)。
  • SOCKS5はポート1080を使用し、java.net.socks.username/password システムプロパティで認証する。
  • 並行スクレイピングでは Semaphore で同時接続数を制限し、ターゲットサーバーへの負荷を抑える。
  • 本番環境ではタイムアウト、リトライ、接続プール、TLS設定を必ず設定する。
  • 倫理的スクレイピングを徹底し、公式APIの利用を優先する。

FAQ

Kotlinでのプロキシ利用とは何ですか?

Kotlinでのプロキシ利用とは、Ktor ClientやOkHttpなどのHTTPクライアントライブラリを経由して、中間プロキシサーバーにリクエストをルーティングすることです。これによりIPアドレスのローテーション、geo-targeting、レート制限の回避などが可能になります。ProxyHatの場合は gate.proxyhat.com:8080(HTTP)または :1080(SOCKS5)をプロキシアドレスとして指定します。

Kotlinでのプロキシ利用がプロキシユーザーにとって重要なのはなぜですか?

データセンタープロキシは多くのウェブサイトやAPIでASNベースのブロック対象になります。レジデンシャルプロキシをKotlinアプリケーションで利用することで、実際のISP IPでリクエストを送信でき、ブロック率を大幅に下げられます。KtorとOkHttpはどちらもプロキシ設定をネイティブサポートしており、Androidとサーバー両方で利用可能です。

Kotlinでのプロキシ利用に最適なプロキシタイプは何ですか?

SNSやe-commerceなどASNブロックが厳しいターゲットではレジデンシャルプロキシが最適です。モバイルAPIやアプリのテストではモバイルプロキシがさらに有効です。データセンターの制限が緩いサイトや高速取得が優先される場合はデータセンタープロキシで十分です。用途に応じて使い分けるのがベストプラクティスです。

Kotlinでのプロキシ利用でブロックを回避するにはどうすればよいですか?

レジデンシャルプロキシの使用、リクエストごとのIPローテーション、適切なUser-Agentの設定、Semaphore による同時接続数の制限、指数バックオフによるリトライを組み合わせることでブロックを回避できます。また、sticky sessionで同一IPを維持しつつセッション単位でローテーションする戦略も有効です。robots.txtの尊重と公式APIの優先も重要です。

ProxyHatのプロキシをKtorで使う場合、認証はどう設定しますか?

Ktor CIOエンジンではプロキシ認証が自動で付与されないため、defaultRequest ブロックで Proxy-Authorization: Basic {base64エンコードされた認証情報} ヘッダーを明示的に追加します。ユーザー名にはgeo-targetingやsticky sessionの情報をエンコードします(例: user-country-DE-city-berlin-session-abc123)。パスワードはProxyHatのダッシュボードで確認できます。

よくある質問

Kotlinでのプロキシ利用とは何ですか?

Kotlinでのプロキシ利用とは、Ktor ClientやOkHttpなどのHTTPクライアントライブラリを経由して、中間プロキシサーバーにリクエストをルーティングすることです。これによりIPアドレスのローテーション、geo-targeting、レート制限の回避などが可能になります。ProxyHatの場合はgate.proxyhat.com:8080(HTTP)または:1080(SOCKS5)をプロキシアドレスとして指定します。

Kotlinでのプロキシ利用がプロキシユーザーにとって重要なのはなぜですか?

データセンタープロキシは多くのウェブサイトやAPIでASNベースのブロック対象になります。レジデンシャルプロキシをKotlinアプリケーションで利用することで、実際のISP IPでリクエストを送信でき、ブロック率を大幅に下げられます。KtorとOkHttpはどちらもプロキシ設定をネイティブサポートしており、Androidとサーバー両方で利用可能です。

Kotlinでのプロキシ利用に最適なプロキシタイプは何ですか?

SNSやe-commerceなどASNブロックが厳しいターゲットではレジデンシャルプロキシが最適です。モバイルAPIやアプリのテストではモバイルプロキシがさらに有効です。データセンターの制限が緩いサイトや高速取得が優先される場合はデータセンタープロキシで十分です。用途に応じて使い分けるのがベストプラクティスです。

Kotlinでのプロキシ利用でブロックを回避するにはどうすればよいですか?

レジデンシャルプロキシの使用、リクエストごとのIPローテーション、適切なUser-Agentの設定、Semaphoreによる同時接続数の制限、指数バックオフによるリトライを組み合わせることでブロックを回避できます。また、sticky sessionで同一IPを維持しつつセッション単位でローテーションする戦略も有効です。robots.txtの尊重と公式APIの優先も重要です。

ProxyHatのプロキシをKtorで使う場合、認証はどう設定しますか?

Ktor CIOエンジンではプロキシ認証が自動で付与されないため、defaultRequestブロックでProxy-Authorization: Basicヘッダーを明示的に追加します。ユーザー名にはgeo-targetingやsticky sessionの情報をエンコードします(例: user-country-DE-city-berlin-session-abc123)。パスワードはProxyHatのダッシュボードで確認できます。

始める準備はできましたか?

148か国以上の住宅用・ISP・モバイルプロキシ。無料アカウントを作成。

無料アカウントを作成
← ブログに戻る