Ktor Client

JetBrains製のKotlinマルチプラットフォーム対応HTTPクライアント。Kotlinコルーチンネイティブサポート、複数エンジン(OkHttp、Apache、Curl等)対応。JVM、Android、JavaScript、Nativeプラットフォームで共通コード実現。DSL形式の直感的API設計。

HTTPクライアントKotlinマルチプラットフォーム非同期WebSocketプラグイン

GitHub概要

ktorio/ktor

Framework for quickly creating connected applications in Kotlin with minimal effort

ホームページ:https://ktor.io
スター14,033
ウォッチ175
フォーク1,193
作成日:2015年8月3日
言語:Kotlin
ライセンス:Apache License 2.0

トピックス

asyncasynchronouskotlinwebweb-framework

スター履歴

ktorio/ktor Star History
データ取得日時: 2025/10/22 09:57

ライブラリ

Ktor Client

概要

Ktor Clientは、JetBrains社が開発したKotlin製のマルチプラットフォームHTTPクライアントフレームワークです。JVM、iOS、JavaScript、Nativeなど様々なプラットフォームで動作し、コルーチンベースの非同期処理、豊富なプラグインシステム、型安全なAPIが特徴。WebSocketやServer-Sent Events (SSE)もサポートし、リアルタイム通信から通常のHTTP APIアクセスまで幅広いユースケースに対応。Kotlinマルチプラットフォーム開発における標準的なHTTPソリューションとして確立されています。

詳細

Ktor Client 2025年版は、Kotlinマルチプラットフォーム(KMP)エコシステムの中核HTTPライブラリとして成熟度を増しています。プラットフォーム固有の最適化エンジン(OkHttp、CIO、Apache等)により各環境で最高のパフォーマンスを発揮し、統一されたAPIで開発体験を提供。コルーチンとの完全統合により直感的な非同期処理を実現し、認証・ログ・キャッシュ・リトライ等の包括的プラグインによる拡張性。Android、iOS、デスクトップ、Web開発で共通のHTTPロジックを共有できる強力なアーキテクチャです。

主な特徴

  • マルチプラットフォーム対応: JVM・iOS・JS・Nativeで統一API
  • コルーチン完全統合: 自然で効率的な非同期処理
  • 豊富なプラグインシステム: 認証・ログ・キャッシュ・リトライ等
  • リアルタイム通信: WebSocket・SSEのネイティブサポート
  • プラットフォーム最適化: 各環境専用エンジンによる高性能
  • 型安全API: Kotlinの型システムを活用した安全性

メリット・デメリット

メリット

  • Kotlinマルチプラットフォーム開発でのコード共有と一貫性
  • コルーチンベースの自然で読みやすい非同期処理コード
  • プラグインアーキテクチャによる高い拡張性とカスタマイズ性
  • 各プラットフォームの最適化エンジンによる高いパフォーマンス
  • WebSocket・SSE等のリアルタイム通信の統合サポート
  • JetBrains社による継続的な開発とコミュニティサポート

デメリット

  • Kotlin専用ライブラリで他言語からの利用は不可
  • マルチプラットフォーム設定の複雑さ(特に初期設定)
  • プラットフォーム固有の制約や挙動の違いが存在
  • 一部のプラグインや機能でプラットフォーム依存性
  • エラーハンドリングがプラットフォームによって異なる場合
  • ドキュメントやサンプルがまだ発展途上の部分あり

参考ページ

書き方の例

依存関係の設定とプロジェクト準備

// build.gradle.kts (共通モジュール)
kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-core:2.3.7")
                implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
                implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
                implementation("io.ktor:ktor-client-logging:2.3.7")
                implementation("io.ktor:ktor-client-auth:2.3.7")
            }
        }
        
        val jvmMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-cio:2.3.7")
                implementation("io.ktor:ktor-client-apache:2.3.7")
                implementation("ch.qos.logback:logback-classic:1.4.14")
            }
        }
        
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:2.3.7")
            }
        }
        
        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.3.7")
            }
        }
        
        val jsMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-js:2.3.7")
            }
        }
    }
}
// プロジェクト設定例
// gradle.properties
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.ignoreDisabledTargets=true

// settings.gradle.kts
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

基本的なHTTPクライアント実装

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable

// データクラス定義
@Serializable
data class User(
    val id: Int? = null,
    val name: String,
    val email: String,
    val age: Int
)

@Serializable
data class ApiResponse<T>(
    val success: Boolean,
    val data: T?,
    val message: String?
)

// 基本的なクライアント設定
class HttpClientExample {
    
    private val client = HttpClient(CIO) {
        install(ContentNegotiation) {
            json()
        }
        install(Logging) {
            logger = Logger.DEFAULT
            level = LogLevel.INFO
        }
    }
    
    // 基本的なGETリクエスト
    suspend fun getUsers(): List<User> {
        return try {
            val response: HttpResponse = client.get("https://api.example.com/users")
            
            println("Status: ${response.status}")
            println("Headers: ${response.headers}")
            
            response.body<List<User>>()
        } catch (e: Exception) {
            println("Error: ${e.message}")
            emptyList()
        }
    }
    
    // クエリパラメータ付きGETリクエスト
    suspend fun getUsersWithPagination(page: Int, limit: Int): List<User> {
        return client.get("https://api.example.com/users") {
            parameter("page", page)
            parameter("limit", limit)
            parameter("sort", "created_at")
            
            headers {
                append(HttpHeaders.Accept, "application/json")
                append(HttpHeaders.UserAgent, "Ktor-Client/1.0")
            }
        }.body()
    }
    
    // POSTリクエスト(JSON送信)
    suspend fun createUser(user: User): User? {
        return try {
            val response: HttpResponse = client.post("https://api.example.com/users") {
                contentType(ContentType.Application.Json)
                setBody(user)
                
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }
            
            if (response.status.isSuccess()) {
                response.body<User>()
            } else {
                println("Error: ${response.status}")
                null
            }
        } catch (e: Exception) {
            println("Error creating user: ${e.message}")
            null
        }
    }
    
    // PUTリクエスト(データ更新)
    suspend fun updateUser(id: Int, user: User): User? {
        return try {
            client.put("https://api.example.com/users/$id") {
                contentType(ContentType.Application.Json)
                setBody(user)
                
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }.body<User>()
        } catch (e: Exception) {
            println("Error updating user: ${e.message}")
            null
        }
    }
    
    // DELETEリクエスト
    suspend fun deleteUser(id: Int): Boolean {
        return try {
            val response = client.delete("https://api.example.com/users/$id") {
                headers {
                    append(HttpHeaders.Authorization, "Bearer your-token")
                }
            }
            
            response.status == HttpStatusCode.NoContent
        } catch (e: Exception) {
            println("Error deleting user: ${e.message}")
            false
        }
    }
    
    fun close() {
        client.close()
    }
}

// 使用例
suspend fun main() = coroutineScope {
    val httpClient = HttpClientExample()
    
    try {
        // ユーザー一覧取得
        val users = httpClient.getUsers()
        println("取得したユーザー数: ${users.size}")
        
        // ページネーション付き取得
        val pagedUsers = httpClient.getUsersWithPagination(1, 10)
        println("ページ取得ユーザー数: ${pagedUsers.size}")
        
        // 新規ユーザー作成
        val newUser = User(name = "田中太郎", email = "[email protected]", age = 30)
        val createdUser = httpClient.createUser(newUser)
        println("作成されたユーザー: $createdUser")
        
        // ユーザー更新
        createdUser?.let { user ->
            val updatedUser = user.copy(name = "田中次郎", age = 31)
            val result = httpClient.updateUser(user.id!!, updatedUser)
            println("更新されたユーザー: $result")
        }
        
        // ユーザー削除
        createdUser?.id?.let { id ->
            val deleted = httpClient.deleteUser(id)
            println("削除成功: $deleted")
        }
        
    } finally {
        httpClient.close()
    }
}

プラグイン設定と高度な機能

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.delay

// 高度なクライアント設定
class AdvancedHttpClient {
    
    val client = HttpClient(CIO) {
        // タイムアウト設定
        install(HttpTimeout) {
            requestTimeoutMillis = 30_000
            connectTimeoutMillis = 10_000
            socketTimeoutMillis = 10_000
        }
        
        // リトライ設定
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 3)
            retryOnException(maxRetries = 3, retryOnTimeout = true)
            exponentialDelay() // 指数バックオフ
        }
        
        // ログ設定
        install(Logging) {
            logger = object : Logger {
                override fun log(message: String) {
                    println("HTTP: $message")
                }
            }
            level = LogLevel.ALL
            filter { request ->
                request.url.host.contains("api.example.com")
            }
        }
        
        // 認証設定
        install(Auth) {
            bearer {
                loadTokens {
                    BearerTokens("your-access-token", "your-refresh-token")
                }
                refreshTokens {
                    // トークンリフレッシュロジック
                    val refreshTokenInfo = client.post("https://api.example.com/auth/refresh") {
                        parameter("refresh_token", oldTokens?.refreshToken)
                    }.body<TokenResponse>()
                    
                    BearerTokens(refreshTokenInfo.accessToken, refreshTokenInfo.refreshToken)
                }
            }
        }
        
        // Cookie管理
        install(HttpCookies) {
            storage = AcceptAllCookiesStorage()
        }
        
        // レスポンス圧縮対応
        install(ContentEncoding) {
            gzip()
            deflate()
        }
        
        // キャッシュ設定
        install(HttpCache)
        
        // カスタムヘッダー設定
        defaultRequest {
            header(HttpHeaders.UserAgent, "MyApp/1.0 (Ktor Client)")
            header("X-API-Version", "v2")
        }
        
        // レスポンス検証
        HttpResponseValidator {
            validateResponse { response ->
                val error = when (response.status.value) {
                    in 300..399 -> "Redirect: ${response.status}"
                    in 400..499 -> "Client error: ${response.status}"
                    in 500..599 -> "Server error: ${response.status}"
                    else -> null
                }
                
                if (error != null) {
                    throw ClientRequestException(response, error)
                }
            }
            
            handleResponseExceptionWithRequest { exception, request ->
                val clientException = exception as? ClientRequestException
                val response = clientException?.response
                
                if (response?.status == HttpStatusCode.Unauthorized) {
                    println("Unauthorized access to ${request.url}")
                }
                
                throw exception
            }
        }
    }
    
    // Bearer認証付きリクエスト
    suspend fun authenticatedRequest(): String {
        return client.get("https://api.example.com/protected") {
            bearerAuth("your-access-token")
        }.body()
    }
    
    // Basic認証
    suspend fun basicAuthRequest(): String {
        return client.get("https://api.example.com/basic-auth") {
            basicAuth("username", "password")
        }.body()
    }
    
    // カスタムヘッダー付きリクエスト
    suspend fun customHeadersRequest(): String {
        return client.get("https://api.example.com/data") {
            headers {
                append("X-Custom-Header", "CustomValue")
                append("X-Request-ID", generateRequestId())
                append("Accept-Language", "ja-JP,en-US")
            }
            
            // SSL設定(開発環境用)
            url {
                protocol = URLProtocol.HTTPS
                host = "secure-api.example.com"
                port = 443
            }
        }.body()
    }
    
    // プロキシ設定例
    suspend fun proxyRequest(): String {
        return HttpClient(CIO) {
            engine {
                proxy = ProxyBuilder.http("http://proxy.example.com:8080")
            }
        }.use { proxyClient ->
            proxyClient.get("https://api.example.com/data").body()
        }
    }
    
    private fun generateRequestId(): String {
        return "req-${System.currentTimeMillis()}-${(1000..9999).random()}"
    }
}

@Serializable
data class TokenResponse(
    val accessToken: String,
    val refreshToken: String,
    val expiresIn: Long
)

WebSocketとServer-Sent Events (SSE)

import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.plugins.sse.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*

// WebSocket通信の実装
class WebSocketClient {
    
    private val client = HttpClient(CIO) {
        install(WebSockets) {
            pingInterval = 20_000
            maxFrameSize = Long.MAX_VALUE
        }
    }
    
    // WebSocket接続とメッセージ送受信
    suspend fun connectWebSocket() {
        client.webSocket(
            method = HttpMethod.Get,
            host = "ws.example.com",
            port = 8080,
            path = "/websocket"
        ) {
            // 接続成功
            println("WebSocket接続完了")
            
            // 並行してメッセージを送受信
            val messageJob = launch {
                try {
                    for (frame in incoming) {
                        when (frame) {
                            is Frame.Text -> {
                                val message = frame.readText()
                                println("受信メッセージ: $message")
                                
                                // JSON解析例
                                try {
                                    val data = Json.decodeFromString<WebSocketMessage>(message)
                                    handleWebSocketMessage(data)
                                } catch (e: Exception) {
                                    println("JSON解析エラー: ${e.message}")
                                }
                            }
                            is Frame.Binary -> {
                                val bytes = frame.readBytes()
                                println("バイナリデータ受信: ${bytes.size} bytes")
                            }
                            is Frame.Close -> {
                                val reason = frame.readReason()
                                println("WebSocket切断: $reason")
                                break
                            }
                            else -> {
                                println("その他のフレーム: ${frame.frameType}")
                            }
                        }
                    }
                } catch (e: Exception) {
                    println("メッセージ受信エラー: ${e.message}")
                }
            }
            
            // 定期的にpingメッセージを送信
            val pingJob = launch {
                while (isActive) {
                    try {
                        send(Frame.Ping("ping".toByteArray()))
                        delay(30_000) // 30秒間隔
                    } catch (e: Exception) {
                        println("Ping送信エラー: ${e.message}")
                        break
                    }
                }
            }
            
            // ユーザーメッセージの送信例
            launch {
                delay(1000) // 接続安定待ち
                
                val messages = listOf(
                    WebSocketMessage("user_join", "田中太郎がチャットに参加しました"),
                    WebSocketMessage("chat_message", "こんにちは!"),
                    WebSocketMessage("typing", "入力中..."),
                    WebSocketMessage("chat_message", "元気ですか?")
                )
                
                for (message in messages) {
                    val json = Json.encodeToString(message)
                    send(Frame.Text(json))
                    println("送信メッセージ: $json")
                    delay(2000)
                }
            }
            
            // 接続維持とクリーンアップ
            try {
                messageJob.join()
            } finally {
                pingJob.cancel()
                println("WebSocket接続終了")
            }
        }
    }
    
    private fun handleWebSocketMessage(message: WebSocketMessage) {
        when (message.type) {
            "chat_message" -> println("チャット: ${message.data}")
            "user_join" -> println("参加通知: ${message.data}")
            "user_leave" -> println("退出通知: ${message.data}")
            "notification" -> println("通知: ${message.data}")
            else -> println("未知のメッセージタイプ: ${message.type}")
        }
    }
    
    fun close() {
        client.close()
    }
}

// Server-Sent Events (SSE) クライアント
class SSEClient {
    
    private val client = HttpClient(CIO) {
        install(SSE)
    }
    
    // SSE接続とイベント受信
    suspend fun connectSSE() {
        client.sse(
            host = "api.example.com",
            port = 443,
            path = "/events"
        ) {
            println("SSE接続開始")
            
            // イベントストリームの処理
            incoming.collect { event ->
                when (event.event) {
                    "message" -> {
                        println("メッセージイベント: ${event.data}")
                    }
                    "notification" -> {
                        val notification = Json.decodeFromString<NotificationEvent>(event.data ?: "")
                        handleNotification(notification)
                    }
                    "status" -> {
                        println("ステータス更新: ${event.data}")
                    }
                    "heartbeat" -> {
                        println("ハートビート: ${event.id}")
                    }
                    else -> {
                        println("未知のイベント [${event.event}]: ${event.data}")
                    }
                }
            }
        }
    }
    
    // 認証付きSSE接続
    suspend fun authenticatedSSE(token: String) {
        client.sse(
            urlString = "https://api.example.com/secured-events",
            request = {
                bearerAuth(token)
                header("Accept", "text/event-stream")
                header("Cache-Control", "no-cache")
            }
        ) {
            incoming.collect { event ->
                println("認証済みイベント [${event.event}]: ${event.data}")
                
                // 再接続ロジック
                if (event.event == "reconnect") {
                    println("サーバーから再接続要求")
                    // ここで再接続処理を実装
                }
            }
        }
    }
    
    private fun handleNotification(notification: NotificationEvent) {
        println("通知: ${notification.title} - ${notification.message}")
        
        when (notification.priority) {
            "high" -> println("⚠️ 重要な通知です")
            "medium" -> println("ℹ️ 通常の通知です")
            "low" -> println("💡 情報通知です")
        }
    }
    
    fun close() {
        client.close()
    }
}

@Serializable
data class WebSocketMessage(
    val type: String,
    val data: String,
    val timestamp: Long = System.currentTimeMillis()
)

@Serializable
data class NotificationEvent(
    val title: String,
    val message: String,
    val priority: String = "medium",
    val timestamp: Long = System.currentTimeMillis()
)

// 使用例
suspend fun webSocketExample() = coroutineScope {
    val wsClient = WebSocketClient()
    
    try {
        wsClient.connectWebSocket()
    } catch (e: Exception) {
        println("WebSocket接続エラー: ${e.message}")
    } finally {
        wsClient.close()
    }
}

suspend fun sseExample() = coroutineScope {
    val sseClient = SSEClient()
    
    try {
        // 通常のSSE接続
        launch {
            sseClient.connectSSE()
        }
        
        // 認証付きSSE接続
        launch {
            sseClient.authenticatedSSE("your-bearer-token")
        }
        
        delay(30_000) // 30秒間接続維持
        
    } catch (e: Exception) {
        println("SSE接続エラー: ${e.message}")
    } finally {
        sseClient.close()
    }
}

エラーハンドリングとリトライ機能

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.seconds

// カスタム例外クラス
sealed class ApiException(message: String, cause: Throwable? = null) : Exception(message, cause) {
    class NetworkException(message: String, cause: Throwable? = null) : ApiException(message, cause)
    class AuthenticationException(message: String) : ApiException(message)
    class AuthorizationException(message: String) : ApiException(message)
    class NotFoundException(message: String) : ApiException(message)
    class RateLimitException(message: String, val retryAfter: Long? = null) : ApiException(message)
    class ServerException(message: String, val statusCode: Int) : ApiException(message)
    class TimeoutException(message: String, cause: Throwable? = null) : ApiException(message, cause)
}

// 堅牢なHTTPクライアント実装
class RobustHttpClient {
    
    private val client = HttpClient(CIO) {
        // タイムアウト設定
        install(HttpTimeout) {
            requestTimeoutMillis = 30_000
            connectTimeoutMillis = 10_000
            socketTimeoutMillis = 15_000
        }
        
        // リトライ設定
        install(HttpRequestRetry) {
            retryOnServerErrors(maxRetries = 3)
            retryOnException(maxRetries = 3, retryOnTimeout = true)
            retryIf(maxRetries = 5) { request, response ->
                response.status == HttpStatusCode.TooManyRequests
            }
            exponentialDelay(base = 2.0, maxDelayMs = 10_000)
            modifyRequest { request ->
                request.headers.append("X-Retry-Count", retryCount.toString())
            }
        }
        
        // エラーレスポンス処理
        HttpResponseValidator {
            validateResponse { response ->
                when (response.status.value) {
                    in 200..299 -> return@validateResponse
                    401 -> throw ApiException.AuthenticationException("認証に失敗しました")
                    403 -> throw ApiException.AuthorizationException("アクセス権限がありません")
                    404 -> throw ApiException.NotFoundException("リソースが見つかりません")
                    429 -> {
                        val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
                        throw ApiException.RateLimitException("レート制限に達しました", retryAfter)
                    }
                    in 500..599 -> throw ApiException.ServerException(
                        "サーバーエラーが発生しました", response.status.value
                    )
                    else -> throw ApiException.NetworkException("HTTP エラー: ${response.status}")
                }
            }
            
            handleResponseExceptionWithRequest { exception, request ->
                when (exception) {
                    is HttpRequestTimeoutException -> {
                        throw ApiException.TimeoutException("リクエストタイムアウト: ${request.url}", exception)
                    }
                    is ConnectTimeoutException -> {
                        throw ApiException.NetworkException("接続タイムアウト: ${request.url}", exception)
                    }
                    else -> throw exception
                }
            }
        }
    }
    
    // 安全なAPIコール実装
    suspend fun <T> safeApiCall(
        request: suspend HttpClient.() -> HttpResponse,
        parser: suspend (HttpResponse) -> T
    ): Result<T> {
        return try {
            val response = client.request()
            val result = parser(response)
            Result.success(result)
        } catch (e: ApiException) {
            println("API例外: ${e.message}")
            Result.failure(e)
        } catch (e: Exception) {
            println("予期しない例外: ${e.message}")
            Result.failure(ApiException.NetworkException("予期しないエラー", e))
        }
    }
    
    // リトライ付きリクエスト(手動実装)
    suspend fun <T> retryableRequest(
        maxRetries: Int = 3,
        initialDelay: Long = 1000,
        maxDelay: Long = 10000,
        request: suspend () -> T
    ): T {
        var currentDelay = initialDelay
        var lastException: Exception? = null
        
        repeat(maxRetries + 1) { attempt ->
            try {
                return request()
            } catch (e: ApiException.RateLimitException) {
                // レート制限の場合は指定された時間待機
                val waitTime = e.retryAfter?.let { it * 1000 } ?: currentDelay
                println("レート制限 - ${waitTime}ms 待機後に再試行 (試行 ${attempt + 1})")
                delay(waitTime)
                lastException = e
            } catch (e: ApiException.ServerException) {
                if (attempt < maxRetries) {
                    println("サーバーエラー - ${currentDelay}ms 待機後に再試行 (試行 ${attempt + 1})")
                    delay(currentDelay)
                    currentDelay = minOf(currentDelay * 2, maxDelay)
                    lastException = e
                } else {
                    throw e
                }
            } catch (e: ApiException.NetworkException) {
                if (attempt < maxRetries) {
                    println("ネットワークエラー - ${currentDelay}ms 待機後に再試行 (試行 ${attempt + 1})")
                    delay(currentDelay)
                    currentDelay = minOf(currentDelay * 2, maxDelay)
                    lastException = e
                } else {
                    throw e
                }
            } catch (e: Exception) {
                throw e // その他の例外は即座に再スロー
            }
        }
        
        throw lastException ?: ApiException.NetworkException("最大試行回数に達しました")
    }
    
    // 使用例:ユーザー取得(エラーハンドリング付き)
    suspend fun getUser(id: Int): Result<User> {
        return safeApiCall(
            request = { 
                get("https://api.example.com/users/$id") {
                    header(HttpHeaders.Accept, "application/json")
                }
            },
            parser = { response -> response.body<User>() }
        )
    }
    
    // 使用例:並列リクエスト(エラーハンドリング付き)
    suspend fun getMultipleUsers(ids: List<Int>): List<Result<User>> {
        return withContext(Dispatchers.IO) {
            ids.map { id ->
                async {
                    retryableRequest {
                        getUser(id).getOrThrow()
                    }
                }.let { deferred ->
                    try {
                        Result.success(deferred.await())
                    } catch (e: Exception) {
                        Result.failure(e)
                    }
                }
            }
        }
    }
    
    // サーキットブレーカーパターンの実装
    class CircuitBreaker(
        private val failureThreshold: Int = 5,
        private val recoveryTimeout: Long = 60_000, // 1分
        private val halfOpenMaxCalls: Int = 3
    ) {
        private var state = State.CLOSED
        private var failureCount = 0
        private var lastFailureTime = 0L
        private var halfOpenCalls = 0
        
        enum class State { CLOSED, OPEN, HALF_OPEN }
        
        suspend fun <T> execute(operation: suspend () -> T): T {
            when (state) {
                State.CLOSED -> {
                    return try {
                        val result = operation()
                        reset()
                        result
                    } catch (e: Exception) {
                        recordFailure()
                        throw e
                    }
                }
                State.OPEN -> {
                    if (System.currentTimeMillis() - lastFailureTime > recoveryTimeout) {
                        state = State.HALF_OPEN
                        halfOpenCalls = 0
                        return execute(operation)
                    } else {
                        throw ApiException.NetworkException("サーキットブレーカーが開いています")
                    }
                }
                State.HALF_OPEN -> {
                    return try {
                        if (halfOpenCalls >= halfOpenMaxCalls) {
                            throw ApiException.NetworkException("ハーフオープン状態での試行回数上限")
                        }
                        halfOpenCalls++
                        val result = operation()
                        reset()
                        result
                    } catch (e: Exception) {
                        state = State.OPEN
                        lastFailureTime = System.currentTimeMillis()
                        throw e
                    }
                }
            }
        }
        
        private fun recordFailure() {
            failureCount++
            lastFailureTime = System.currentTimeMillis()
            if (failureCount >= failureThreshold) {
                state = State.OPEN
            }
        }
        
        private fun reset() {
            failureCount = 0
            state = State.CLOSED
        }
        
        fun getState(): State = state
        fun getFailureCount(): Int = failureCount
    }
    
    fun close() {
        client.close()
    }
}

// 使用例
suspend fun errorHandlingExample() = coroutineScope {
    val robustClient = RobustHttpClient()
    val circuitBreaker = RobustHttpClient.CircuitBreaker()
    
    try {
        // 単一ユーザー取得
        val userResult = robustClient.getUser(123)
        userResult.fold(
            onSuccess = { user -> println("ユーザー取得成功: $user") },
            onFailure = { error -> println("ユーザー取得失敗: ${error.message}") }
        )
        
        // 複数ユーザー並列取得
        val userIds = listOf(1, 2, 3, 4, 5)
        val results = robustClient.getMultipleUsers(userIds)
        
        results.forEachIndexed { index, result ->
            result.fold(
                onSuccess = { user -> println("ユーザー ${userIds[index]} 取得成功: $user") },
                onFailure = { error -> println("ユーザー ${userIds[index]} 取得失敗: ${error.message}") }
            )
        }
        
        // サーキットブレーカー使用例
        repeat(10) { i ->
            try {
                circuitBreaker.execute {
                    robustClient.getUser(i).getOrThrow()
                }
                println("サーキットブレーカー経由でユーザー取得成功: $i")
            } catch (e: Exception) {
                println("サーキットブレーカー経由失敗: ${e.message}")
                println("サーキットブレーカー状態: ${circuitBreaker.getState()}")
            }
            
            delay(1000)
        }
        
    } finally {
        robustClient.close()
    }
}

マルチプラットフォーム対応とプラットフォーム固有実装

// common/src/commonMain/kotlin/HttpClientFactory.kt
expect object HttpClientFactory {
    fun create(): HttpClient
}

// common/src/commonMain/kotlin/ApiClient.kt
class ApiClient {
    private val client = HttpClientFactory.create()
    
    suspend fun getData(): String {
        return client.get("https://api.example.com/data").body()
    }
    
    fun close() {
        client.close()
    }
}

// jvm/src/jvmMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.cio.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(CIO) {
        engine {
            maxConnectionsCount = 1000
            endpoint {
                maxConnectionsPerRoute = 100
                pipelineMaxSize = 20
                keepAliveTime = 5000
                connectTimeout = 5000
                connectAttempts = 5
            }
        }
    }
}

// android/src/androidMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(OkHttp) {
        engine {
            preconfigured = OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .build()
        }
    }
}

// ios/src/iosMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(Darwin) {
        engine {
            configureRequest {
                setAllowsCellularAccess(true)
                setAllowsExpensiveNetworkAccess(true)
                setAllowsConstrainedNetworkAccess(true)
            }
        }
    }
}

// js/src/jsMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.js.*

actual object HttpClientFactory {
    actual fun create(): HttpClient = HttpClient(Js) {
        engine {
            // JavaScript engine specific configuration
        }
    }
}

// 使用例:プラットフォーム共通のAPI呼び出し
// common/src/commonMain/kotlin/UserRepository.kt
class UserRepository {
    private val apiClient = ApiClient()
    
    suspend fun getUsers(): List<User> {
        return try {
            val response = apiClient.client.get("https://api.example.com/users")
            response.body<List<User>>()
        } catch (e: Exception) {
            println("Error fetching users: ${e.message}")
            emptyList()
        }
    }
    
    suspend fun createUser(user: User): User? {
        return try {
            val response = apiClient.client.post("https://api.example.com/users") {
                contentType(ContentType.Application.Json)
                setBody(user)
            }
            response.body<User>()
        } catch (e: Exception) {
            println("Error creating user: ${e.message}")
            null
        }
    }
    
    fun close() {
        apiClient.close()
    }
}

// 各プラットフォームでの使用例
suspend fun multiplatformExample() {
    val userRepository = UserRepository()
    
    try {
        // すべてのプラットフォームで同じコードが動作
        val users = userRepository.getUsers()
        println("取得したユーザー数: ${users.size}")
        
        val newUser = User(name = "テストユーザー", email = "[email protected]", age = 25)
        val createdUser = userRepository.createUser(newUser)
        
        createdUser?.let {
            println("ユーザー作成成功: ${it.name}")
        }
        
    } finally {
        userRepository.close()
    }
}