Skip to content

网络:Ktor Client

源:Ktor Client Documentation

Ktor Client 是 Kotlin Multiplatform 官方推荐的网络库,提供了统一的 API 来处理跨平台 HTTP 请求。

基础配置

kotlin
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

// 创建通用客户端
fun createHttpClient(): HttpClient {
    return HttpClient {
        // JSON 序列化
        install(ContentNegotiation) {
            json(Json {
                ignoreUnknownKeys = true
                prettyPrint = true
                isLenient = true
            })
        }
        
        // 日志插件
        install(Logging) {
            level = LogLevel.BODY
            logger = object : Logger {
                override fun log(message: String) {
                    println("[HTTP] $message")
                }
            }
        }
        
        // 默认请求配置
        defaultRequest {
            url("https://api.example.com/")
        }
    }
}
kotlin
import io.ktor.client.*
import io.ktor.client.engine.android.*

actual fun createPlatformHttpClient(): HttpClient {
    return HttpClient(Android) {
        engine {
            connectTimeout = 30_000
            socketTimeout = 30_000
        }
    }
}
kotlin
import io.ktor.client.*
import io.ktor.client.engine.darwin.*

actual fun createPlatformHttpClient(): HttpClient {
    return HttpClient(Darwin) {
        engine {
            configureRequest {
                setAllowsCellularAccess(true)
            }
        }
    }
}
kotlin
import io.ktor.client.*
import io.ktor.client.engine.cio.*

actual fun createPlatformHttpClient(): HttpClient {
    return HttpClient(CIO) {
        engine {
            maxConnectionsCount = 1000
            endpoint {
                maxConnectionsPerRoute = 100
                connectTimeout = 30_000
            }
        }
    }
}

依赖配置

toml
[versions]
ktor = "3.0.3"
kotlinx-serialization = "1.7.3"

[libraries]
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }

# 平台引擎
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }

[bundles]
ktor-common = [
    "ktor-client-core",
    "ktor-client-content-negotiation",
    "ktor-serialization-kotlinx-json",
    "ktor-client-logging"
]
kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.bundles.ktor.common)
        }
        
        androidMain.dependencies {
            implementation(libs.ktor.client.android)
        }
        
        iosMain.dependencies {
            implementation(libs.ktor.client.darwin)
        }
        
        jvmMain.dependencies {
            implementation(libs.ktor.client.cio)
        }
    }
}

最新版本查询:https://github.com/ktorio/ktor/releases

HTTP 请求

GET 请求

kotlin
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.call.*

// 简单 GET
suspend fun fetchText(): String {
    return client.get("https://api.example.com/data").bodyAsText()
}

// 带参数 GET
suspend fun searchUsers(query: String): List<User> {
    return client.get("https://api.example.com/users") {
        parameter("q", query)
        parameter("limit", 20)
    }.body()
}

// 带 Header
suspend fun fetchWithAuth(token: String): String {
    return client.get("https://api.example.com/protected") {
        header("Authorization", "Bearer $token")
    }.bodyAsText()
}

POST 请求

kotlin
import io.ktor.client.request.*
import io.ktor.http.*

// JSON 请求
suspend fun createUser(user: User): User {
    return client.post("https://api.example.com/users") {
        contentType(ContentType.Application.Json)
        setBody(user)
    }.body()
}

// 表单请求
suspend fun login(username: String, password: String): AuthResponse {
    return client.post("https://api.example.com/auth/login") {
        setBody(FormDataContent(Parameters.build {
            append("username", username)
            append("password", password)
        }))
    }.body()
}

// Multipart 文件上传
import io.ktor.client.request.forms.*
import io.ktor.http.content.*

suspend fun uploadFile(file: ByteArray, filename: String): UploadResponse {
    return client.post("https://api.example.com/upload") {
        setBody(MultiPartFormDataContent(
            formData {
                append("file", file, Headers.build {
                    append(HttpHeaders.ContentType, "image/png")
                    append(HttpHeaders.ContentDisposition, "filename=$filename")
                })
            }
        ))
    }.body()
}

PUT/DELETE 请求

kotlin
// PUT 更新
suspend fun updateUser(id: String, user: User): User {
    return client.put("https://api.example.com/users/$id") {
        contentType(ContentType.Application.Json)
        setBody(user)
    }.body()
}

// DELETE 删除
suspend fun deleteUser(id: String): Boolean {
    val response = client.delete("https://api.example.com/users/$id")
    return response.status.isSuccess()
}

插件配置

JSON 序列化

kotlin
install(ContentNegotiation) {
    json(Json {
        prettyPrint = true
        isLenient = true
        ignoreUnknownKeys = true
        explicitNulls = false
        encodeDefaults = true
    })
}

// 使用
@Serializable
data class ApiResponse<T>(
    val code: Int,
    val message: String,
    val data: T?
)

suspend fun getUser(id: String): ApiResponse<User> {
    return client.get("/users/$id").body()
}

请求/响应拦截器

kotlin
import io.ktor.client.plugins.*

// 添加通用请求头
install(DefaultRequest) {
    header("X-Api-Key", "YOUR_API_KEY")
    header("User-Agent", "MyApp/1.0")
}

// 响应验证
install(HttpCallValidator) {
    validateResponse { response ->
        if (response.status.value >= 400) {
            val error = response.bodyAsText()
            throw ApiException(response.status.value, error)
        }
    }
}

class ApiException(val code: Int, message: String) : Exception(message)

认证

kotlin
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*

// Bearer Token
install(Auth) {
    bearer {
        loadTokens {
            BearerTokens(
                accessToken = loadAccessToken(),
                refreshToken = loadRefreshToken()
            )
        }
        
        refreshTokens {
            val newTokens = refreshAccessToken(oldTokens?.refreshToken)
            BearerTokens(
                accessToken = newTokens.accessToken,
                refreshToken = newTokens.refreshToken
            )
        }
    }
}

// Basic Auth
install(Auth) {
    basic {
        credentials {
            BasicAuthCredentials(
                username = "user",
                password = "password"
            )
        }
    }
}

超时配置

kotlin
import io.ktor.client.plugins import io.ktor.client.plugins.*

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

重试机制

kotlin
import io.ktor.client.plugins.*

install(HttpRequestRetry) {
    retryOnServerErrors(maxRetries = 3)
    exponentialDelay()
    
    modifyRequest { request ->
        request.headers.append("X-Retry-Count", retryCount.toString())
    }
}

高级用法

流式下载

kotlin
import io.ktor.utils.io.*
import java.io.File

suspend fun downloadFile(url: String, outputFile: File) {
    client.get(url).bodyAsChannel().copyTo(outputFile.outputStream())
}

// 带进度
suspend fun downloadWithProgress(
    url: String,
    outputFile: File,
    onProgress: (Float) -> Unit
) {
    val response = client.get(url)
    val contentLength = response.headers["Content-Length"]?.toLong() ?: -1L
    var downloaded = 0L
    
    val channel = response.bodyAsChannel()
    outputFile.outputStream().use { output ->
        while (!channel.isClosedForRead) {
            val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
            while (!packet.isEmpty) {
                val bytes = packet.readBytes()
                output.write(bytes)
                downloaded += bytes.size
                
                if (contentLength > 0) {
                    onProgress(downloaded.toFloat() / contentLength)
                }
            }
        }
    }
}

并发请求

kotlin
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope

suspend fun fetchMultipleUsers(ids: List<String>): List<User> = coroutineScope {
    ids.map { id ->
        async {
            client.get("https://api.example.com/users/$id").body<User>()
        }
    }.awaitAll()
}

WebSocket 支持

kotlin
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*

install(WebSockets)

suspend fun connectWebSocket() {
    client.webSocket("wss://example.com/ws") {
        // 发送消息
        send("Hello, Server!")
        
        // 接收消息
        for (frame in incoming) {
            when (frame) {
                is Frame.Text -> {
                    val text = frame.readText()
                    println("Received: $text")
                }
                else -> {}
            }
        }
    }
}

最佳实践

✅ 实践 1:封装 API 服务

kotlin
class UserApiService(private val client: HttpClient) {
    private val baseUrl = "https://api.example.com"
    
    suspend fun getUsers(): List<User> {
        return client.get("$baseUrl/users").body()
    }
    
    suspend fun getUser(id: String): User {
        return client.get("$baseUrl/users/$id").body()
    }
    
    suspend fun createUser(user: User): User {
        return client.post("$baseUrl/users") {
            setBody(user)
        }.body()
    }
}

// 使用
val api = UserApiService(createHttpClient())
val users = api.getUsers()

✅ 实践 2:统一错误处理

kotlin
sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val code: Int, val message: String) : ApiResult<Nothing>()
}

suspend inline fun <reified T> safeApiCall(
    crossinline call: suspend () -> T
): ApiResult<T> {
    return try {
        ApiResult.Success(call())
    } catch (e: ClientRequestException) {
        ApiResult.Error(e.response.status.value, e.message)
    } catch (e: Exception) {
        ApiResult.Error(-1, e.message ?: "Unknown error")
    }
}

// 使用
val result = safeApiCall { client.get("/users").body<List<User>>() }
when (result) {
    is ApiResult.Success -> println(result.data)
    is ApiResult.Error -> println("Error: ${result.message}")
}

✅ 实践 3:单例 HttpClient

kotlin
// ✅ 复用 HttpClient 实例
object ApiClient {
    val instance: HttpClient by lazy { createHttpClient() }
}

// ❌ 避免频繁创建
suspend fun fetchData() {
    val client = HttpClient() // 每次都创建新实例,浪费资源
    client.get("/data")
    client.close()
}

✅ 实践 4:资源释放

kotlin
// 应用关闭时释放
fun onAppShutdown() {
    ApiClient.instance.close()
}

Ktor Client 提供了强大而灵活的跨平台网络能力,配合 Kotlin 协程和序列化,可以构建高效、类型安全的网络层。