Kotlin 与 ktor-client
Ktor HTTP客户端
前置知识
- Kotlin 与 Koin:建议先完成前一篇的学习
学习目标
- 掌握「概述」的核心机制、典型用法与常见陷阱
- 掌握「基础概念」的核心机制、典型用法与常见陷阱
- 掌握「快速上手」的核心机制、典型用法与常见陷阱
- 掌握「详细用法」的核心机制、典型用法与常见陷阱
- 掌握「常见场景」的核心机制、典型用法与常见陷阱
概述
Ktor Client 是 JetBrains 开发的 Kotlin HTTP 客户端库。它完全基于协程构建,支持多种引擎(CIO、OkHttp、Darwin 等),可以运行在 JVM、Android、iOS 和原生平台上。如果你需要在 Kotlin 项目中调用 REST API、下载文件或与 Web 服务交互,Ktor Client 是一个轻量且 Kotlin 风格的选择。
与其他 HTTP 客户端(如 Retrofit、OkHttp)相比,Ktor Client 的优势在于:原生协程支持、DSL 风格配置、多平台兼容、插件化架构。
基础概念
- Engine(引擎):底层 HTTP 实现,如 CIO(Ktor 自带)、OkHttp、Darwin(iOS 原生)。不同平台可以选择不同引擎
- Plugin(插件):Ktor 通过插件扩展功能,比如 ContentNegotiation(JSON 序列化)、Logging(日志)、Auth(认证)
- HttpClient:核心客户端对象,所有请求都通过它发出
- HttpRequest/HttpResponse:请求和响应对象,包含头信息、状态码、正文等
快速上手
添加依赖:
// build.gradle.kts
dependencies {
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-cio:2.3.7")
// JSON 支持
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
}
最简单的 GET 请求:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
suspend fun main() {
// 创建客户端,使用 CIO 引擎
val client = HttpClient(CIO)
// 发送 GET 请求
val response: HttpResponse = client.get("https://httpbin.org/get")
println("状态码: ${response.status.value}")
println("响应体: ${response.bodyAsText()}")
// 关闭客户端
client.close()
}
详细用法
配置客户端
通过 DSL 配置客户端和插件:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
val client = HttpClient(CIO) {
// 安装 ContentNegotiation 插件,自动序列化/反序列化 JSON
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true // 忽略未知字段
prettyPrint = true // 格式化输出
isLenient = true // 宽松解析
})
}
// 配置默认请求参数
defaultRequest {
url("https://api.example.com/")
// 添加公共请求头
headers.append("X-App-Version", "1.0")
}
}
GET 请求与 JSON 解析
import kotlinx.serialization.Serializable
// 定义数据类,必须标记 @Serializable
@Serializable
data class User(val id: Int, val name: String, val email: String)
@Serializable
data class UserListResponse(val users: List<User>)
// 获取单个用户
suspend fun getUser(id: Int): User {
return client.get("users/$id").body<User>()
}
// 获取用户列表
suspend fun getUsers(): List<User> {
val response = client.get("users").body<UserListResponse>()
return response.users
}
// 带查询参数的请求
suspend fun searchUsers(query: String, page: Int): List<User> {
return client.get("users/search") {
// 添加查询参数
url {
parameters.append("q", query)
parameters.append("page", page.toString())
}
}.body<UserListResponse>().users
}
POST/PUT/DELETE 请求
@Serializable
data class CreateUserRequest(val name: String, val email: String)
// POST 请求:创建资源
suspend fun createUser(name: String, email: String): User {
return client.post("users") {
// 设置请求头
contentType(ContentType.Application.Json)
// 设置请求体
setBody(CreateUserRequest(name, email))
}.body<User>()
}
// PUT 请求:更新资源
suspend fun updateUser(id: Int, name: String, email: String): User {
return client.put("users/$id") {
contentType(ContentType.Application.Json)
setBody(CreateUserRequest(name, email))
}.body<User>()
}
// DELETE 请求:删除资源
suspend fun deleteUser(id: Int) {
val response = client.delete("users/$id")
println("删除结果: ${response.status.value}")
}
处理请求头和响应头
// 自定义请求头
suspend fun requestWithAuth(token: String): String {
val response = client.get("protected/data") {
headers {
append("Authorization", "Bearer $token")
append("Accept", "application/json")
}
}
// 读取响应头
val contentType = response.headers["Content-Type"]
val server = response.headers["Server"]
println("Content-Type: $contentType, Server: $server")
return response.bodyAsText()
}
文件下载与上传
import okio.FileSystem
import okio.Path.Companion.toPath
// 下载文件
suspend fun downloadFile(url: String, savePath: String) {
val response = client.get(url)
// 将响应体写入文件
val fileData = response.body<ByteArray>()
FileSystem.SYSTEM.write(savePath.toPath()) {
write(fileData)
}
println("文件已保存到: $savePath")
}
// 上传文件(multipart)
suspend fun uploadFile(filePath: String): String {
return client.submitFormWithBinaryData(
url = "upload",
formData = formData {
// 添加文件
append("file", File(filePath).readBytes(), Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"${File(filePath).name}\"")
})
// 添加普通字段
append("description", "上传的文件")
}
).bodyAsText()
}
常见场景
调用 RESTful API
class ApiClient(private val client: HttpClient) {
// 获取分页数据
suspend fun getItems(page: Int, size: Int = 20): PageResult<Item> {
return client.get("items") {
url {
parameters.append("page", page.toString())
parameters.append("size", size.toString())
}
}.body()
}
// 提交表单数据
suspend fun submitForm(name: String, value: String): Result {
return client.submitForm(
url = "submit",
formParameters = Parameters.build {
append("name", name)
append("value", value)
}
).body()
}
}
添加日志和超时
import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.*
val client = HttpClient(CIO) {
// 日志插件
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.BODY // 打印完整请求和响应体
}
// 超时配置
install(HttpTimeout) {
connectTimeoutMillis = 5000 // 连接超时 5 秒
requestTimeoutMillis = 10000 // 请求超时 10 秒
socketTimeoutMillis = 15000 // 读取超时 15 秒
}
}
错误处理
import io.ktor.client.plugins.*
suspend fun safeRequest(): Result<User> {
return try {
val user = client.get("users/1").body<User>()
Result.success(user)
} catch (e: ClientRequestException) {
// 4xx 错误
println("客户端错误: ${e.response.status.value}")
Result.failure(e)
} catch (e: ServerResponseException) {
// 5xx 错误
println("服务器错误: ${e.response.status.value}")
Result.failure(e)
} catch (e: HttpRequestTimeoutException) {
// 请求超时
println("请求超时")
Result.failure(e)
} catch (e: Exception) {
// 其他错误
println("未知错误: ${e.message}")
Result.failure(e)
}
}
注意事项
- 必须关闭客户端:HttpClient 使用完后要调用
close(),或者用use函数自动关闭 - 协程中使用:所有请求方法都是 suspend 函数,必须在协程或另一个 suspend 函数中调用
- 引擎选择:CIO 是纯 Kotlin 实现,跨平台兼容;OkHttp 在 JVM 上性能更好;Darwin 用于 iOS 原生
- 不要在主线程请求:Android 上要在 IO 调度器上发起网络请求
- JSON 序列化:数据类必须添加
@Serializable注解,且需要引入 kotlinx-serialization 插件
进阶用法
Cookie 管理
import io.ktor.client.plugins.cookies.*
val client = HttpClient(CIO) {
// 启用 Cookie 管理
install(HttpCookies) {
// 使用默认的内存 Cookie 存储
storage = AcceptAllCookiesStorage()
}
}
// 后续请求会自动携带和保存 Cookie
认证插件
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
val client = HttpClient(CIO) {
install(Auth) {
// Basic 认证
basic {
credentials {
BasicAuthCredentials(username = "admin", password = "secret")
}
}
// Bearer Token 认证
bearer {
loadTokens {
BearerTokens(accessToken = "your-token", refreshToken = "refresh-token")
}
// Token 过期时自动刷新
refreshTokens {
val newToken = refreshToken() // 调用刷新接口
BearerTokens(accessToken = newToken, refreshToken = null)
}
}
}
}
响应验证
import io.ktor.client.plugins.*
val client = HttpClient(CIO) {
// 自定义响应验证
install(HttpResponseValidator) {
// 验证响应状态码
validateResponse { response ->
if (response.status.value >= 400) {
throw CustomApiException(response.status.value, response.bodyAsText())
}
}
// 处理响应异常
handleResponseException { request, cause ->
println("请求 ${request.url} 失败: ${cause.message}")
}
}
}
class CustomApiException(val code: Int, val body: String) : Exception("API 错误 $code: $body")
多平台共享代码
Ktor Client 支持 Kotlin Multiplatform,可以在共享模块中编写网络代码:
// commonMain 中编写通用代码
expect val httpClientEngine: HttpClientEngine
val sharedClient = HttpClient(httpClientEngine) {
install(ContentNegotiation) { json() }
}
// androidMain 中提供 OkHttp 引擎
actual val httpClientEngine: HttpClientEngine = OkHttp.create()
// iosMain 中提供 Darwin 引擎
actual val httpClientEngine: HttpClientEngine = Darwin.create()
创建客户端
基本写法:创建 HttpClient
HttpClient(<引擎>)
// 创建 HttpClient
val client = HttpClient(CIO)
基本写法:带配置创建
HttpClient(<引擎>) { }
// 配置引擎参数
val client = HttpClient(CIO) {
engine { requestTimeout = 5000 }
}
基本写法:OkHttp 引擎
HttpClient(OkHttp)
// 使用 OkHttp 引擎
val client = HttpClient(OkHttp)
请求方法
基本写法:GET 请求
client.get("<url>")
// 发送 GET 请求
val resp = client.get("https://api.example.com/users")
基本写法:POST 请求
client.post("<url>") { }
// 发送 POST 请求
client.post("https://api.example.com/users") {
setBody(User("Alice"))
}
基本写法:PUT 请求
client.put("<url>") { }
// 发送 PUT 请求
client.put("https://api.example.com/users/1") { setBody(data) }
基本写法:DELETE 请求
client.delete("<url>")
// 发送 DELETE 请求
client.delete("https://api.example.com/users/1")
请求配置
基本写法:设置请求头
headers { append("<名称>", "<值>") }
// 添加请求头
client.get("...") {
header("Authorization", "Bearer token")
}
基本写法:URL 参数
url { parameters.append("<名>", "<值>") }
// 添加查询参数
client.get("...") {
url { parameters.append("page", "1") }
}
基本写法:JSON 请求体
setBody(<对象>)
// 发送 JSON 请求体
client.post("...") {
contentType(ContentType.Application.Json)
setBody(user)
}
响应处理
基本写法:获取响应体文本
<resp>.bodyAsText()
// 获取响应文本
val text = client.get("...").bodyAsText()
基本写法:反序列化为对象
<resp>.body<<类型>>()
// 反序列化响应体
val user = client.get("...").body<User>()
基本写法:获取状态码
<resp>.status
// 获取 HTTP 状态码
val status = resp.status
基本写法:获取响应头
<resp>.headers["<名称>"]
// 获取响应头
val ct = resp.headers[HttpHeaders.ContentType]
ContentNegotiation 插件
基本写法:安装 JSON 插件
install(ContentNegotiation) { json() }
// 启用自动 JSON 序列化
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
}
HttpRequestBuilder 风格
基本写法:配置请求构建器
HttpRequestBuilder().apply { }
// 复用请求配置
val builder = HttpRequestBuilder().apply {
url("https://api.example.com")
header("X-Key", "abc")
}
client.request(builder)
超时配置
基本写法:设置超时
install(HttpTimeout) { }
// 配置请求超时
install(HttpTimeout) {
requestTimeoutMillis = 5000
connectTimeoutMillis = 3000
socketTimeoutMillis = 10000
}
鉴权插件
基本写法:Bearer 鉴权
install(Auth) { bearer { } }
// Bearer Token 自动加载
install(Auth) {
bearer {
loadTokens { BearerTokens(accessToken, refreshToken) }
}
}
基本写法:Basic 鉴权
install(Auth) { basic { } }
// Basic 认证
install(Auth) {
basic {
username = "user"
password = "pwd"
}
}
日志插件
基本写法:启用日志
install(Logging) { }
// 请求响应日志
install(Logging) {
level = LogLevel.HEADERS
logger = Logger.DEFAULT
}
重试插件
基本写法:失败重试
install(HttpRequestRetry) { }
// 配置重试策略
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
exponentialDelay()
}
UserAgent 插件
基本写法:设置 UA
install(UserAgent) { agent = "<ua>" }
// 设置全局 User-Agent
install(UserAgent) { agent = "MyApp/1.0" }
HttpCookies Cookie 管理
基本写法:启用 Cookie
install(HttpCookies)
// 自动管理 Cookie
install(HttpCookies)
基本写法:设置 Cookie 存储
install(HttpCookies) { storage = <存储> }
// 自定义 Cookie 存储
install(HttpCookies) {
storage = AcceptAllCookiesStorage()
}
WebSocket 客户端
基本写法:建立 WebSocket
client.webSocket("<url>") { }
// 建立 WebSocket 连接
client.webSocket("wss://echo.example.com") {
send(Frame.Text("hello"))
}
基本写法:发送 WebSocket 消息
send(Frame.Text("<消息>"))
// 发送文本帧
send(Frame.Text("ping"))
基本写法:接收 WebSocket 消息
incoming.receive() as Frame.Text
// 接收文本帧
val msg = (incoming.receive() as Frame.Text).readText()
SSE Server-Sent Events
基本写法:SSE 接收
client.config { }.sseSession { }
// 处理 SSE 事件流
client.sseSession("https://events.example.com") {
incoming.collect { event -> println(event.data) }
}
资源关闭
基本写法:关闭客户端
<client>.close()
// 关闭释放连接池
client.close()
基本写法:use 自动关闭
HttpClient(<引擎>).use { }
// use 块自动关闭
HttpClient(CIO).use { c -> c.get("...") }
跨平台使用
基本写法:KMP 共享客户端
val client = HttpClient()
// KMP 跨平台默认引擎
val client = HttpClient()