Ktor Client
JetBrains製のKotlinマルチプラットフォーム対応HTTPクライアント。Kotlinコルーチンネイティブサポート、複数エンジン(OkHttp、Apache、Curl等)対応。JVM、Android、JavaScript、Nativeプラットフォームで共通コード実現。DSL形式の直感的API設計。
GitHub概要
ktorio/ktor
Framework for quickly creating connected applications in Kotlin with minimal effort
トピックス
スター履歴
ライブラリ
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()
}
}