1. 概述
文件上传是大多数 Web 应用中的常见需求。在 Kotlin 生态中,有多个成熟的 HTTP 客户端库可以优雅地处理文件上传任务。
本文将带你实战使用四种主流库完成文件上传:Fuel、OkHttp、Ktor 和 Retrofit。每种方案都有其适用场景,掌握它们能让你在不同项目中灵活选型,避免踩坑。
✅ 所有示例均通过 WireMock 验证请求正确性,确保代码可落地。
2. 使用 Fuel 上传文件
Fuel 是一个专为 Kotlin 设计的轻量级 HTTP 客户端,API 简洁直观,特别适合快速集成文件上传功能。
核心方法是 httpUpload()
,配合 FileDataPart
构建 multipart 请求体:
fun uploadFileFuel(filePath: String, uploadUrl: String) {
val file = File(filePath)
uploadUrl.httpUpload()
.add(FileDataPart(file, name = "file"))
.response { request, response, result ->
println(response)
}.get()
}
测试验证(WireMock)
我们使用 JUnit 的 WireMockRule
模拟服务端行为,确认请求头和内容是否符合预期:
@Rule
@JvmField
val wireMockRule = WireMockRule(8080)
@Test
fun `Should upload file using Fuel`() {
stubFor(post(urlEqualTo("/upload")).willReturn(aResponse().withStatus(200)))
uploadFileFuel("testfile.txt", "http://localhost:8080/upload")
verify(
postRequestedFor(urlEqualTo("/upload"))
.withHeader("Content-Type", containing("multipart/form-data"))
.withRequestBody(matching(".*testfile.txt.*"))
)
}
⚠️ 注意:add(FileDataPart(...))
会自动设置正确的 Content-Disposition 和 Content-Type。
3. 使用 Ktor 实现文件上传
Ktor 是 JetBrains 推出的异步全栈框架,客户端模块同样强大且高度可定制,非常适合现代非阻塞应用。
使用 submitFormWithBinaryData
可轻松构建二进制表单上传请求:
suspend fun uploadFileKtor(filePath: String, uploadUrl: String) {
val client = HttpClient(CIO) {}
val statement: HttpStatement = client.submitFormWithBinaryData(
url = uploadUrl,
formData = formData {
append("file", File(filePath).readBytes(), Headers.build {
append(HttpHeaders.ContentType, "application/octet-stream")
append(HttpHeaders.ContentDisposition, "filename=${File(filePath).name}")
})
}
)
println(statement.execute().readText())
client.close()
}
关键点说明
- ✅ 使用
CIO
作为底层引擎(协程驱动) - ✅
formData { }
DSL 风格清晰易读 - ✅ 自动封装为
multipart/form-data
单元测试
与前文一致,仍用 WireMock 校验请求发出情况:
@Test
fun `Should upload file using Ktor`() = runBlocking {
stubFor(post(urlEqualTo("/upload")).willReturn(aResponse().withStatus(200)))
uploadFileKtor("testfile.txt", "http://localhost:8080/upload")
verify(
postRequestedFor(urlEqualTo("/upload"))
.withRequestBody(matching(".*testfile.txt.*"))
.withHeader("Content-Type", containing("multipart/form-data"))
)
}
⚠️ 别忘了加上
runBlocking
,因为这是 suspend 函数。
4. 使用 OkHttp 执行文件上传
OkHttp 是 Android 和 JVM 领域的事实标准 HTTP 客户端,性能强、稳定性高, Retrofit 也默认基于它构建。
虽然 API 相对 verbose,但控制粒度更细,适合复杂场景:
fun uploadFileOkHttp(filePath: String, uploadUrl: String) {
val client = OkHttpClient()
val file = File(filePath)
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.name, file.asRequestBody("application/octet-stream".toMediaTypeOrNull()))
.build()
val request = Request.Builder()
.url(uploadUrl)
.post(requestBody)
.build()
println(client.newCall(request).execute())
}
核心组件解析
组件 | 作用 |
---|---|
OkHttpClient |
发起网络请求的客户端实例 |
MultipartBody.Builder |
构建多部分表单数据 |
addFormDataPart |
添加文件字段,支持指定名称、文件名和 RequestBody |
execute() |
同步执行请求 |
测试代码
@Test
fun `Should upload file using OkHttp`() {
stubFor(post(urlEqualTo("/upload")).willReturn(aResponse().withStatus(200)))
uploadFileOkHttp("testfile.txt", "http://localhost:8080/upload")
verify(
postRequestedFor(urlEqualTo("/upload"))
.withHeader("Content-Type", containing("multipart/form-data"))
.withRequestBody(matching(".*testfile.txt.*"))
)
}
✅ 小贴士:若需异步上传,可用 enqueue()
替代 execute()
。
5. 使用 Retrofit 进行类型安全的文件上传
Retrofit 是 Square 开发的类型安全 HTTP 客户端,广泛用于 Android 和后端服务。它基于 OkHttp,通过注解 + 接口契约方式简化网络调用。
5.1 定义上传接口
必须使用 @Multipart
注解标记接口方法,并用 @Part
传入文件部分:
interface UploadService {
@Multipart
@POST("upload")
fun uploadFile(@Part file: MultipartBody.Part): Call<ResponseBody>
}
❌ 错误示范:忘记加
@Multipart
会导致请求体格式错误!
5.2 创建 Retrofit Service 实例
配置基础 URL、OkHttpClient 和 Converter(如 Gson):
fun createUploadService(url: String): UploadService {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.client(OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(UploadService::class.java)
}
5.3 调用上传方法
手动构造 MultipartBody.Part
并触发同步请求:
fun uploadFileRetrofit(filePath: String, uploadUrl: String) {
val file = File(filePath)
val requestBody = file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
val multipartBody = MultipartBody.Part.createFormData("file", file.name, requestBody)
val service = createUploadService(uploadUrl)
val call = service.uploadFile(multipartBody)
println(call.execute().body()?.string())
}
测试上传逻辑
依然是熟悉的套路,验证请求是否按预期发出:
@Test
fun `Should upload file using Retrofit`() {
stubFor(post(urlEqualTo("/upload")).willReturn(aResponse().withStatus(200)))
uploadFileRetrofit("testfile.txt", "http://localhost:8080/upload")
verify(
postRequestedFor(urlEqualTo("/upload"))
.withHeader("Content-Type", containing("multipart/form-data"))
.withRequestBody(matching(".*testfile.txt.*"))
)
}
✅ 提示:生产环境建议结合 RxJava 或 Coroutine Call Adapter 实现异步调用。
6. 总结
库名 | 特点 | 适用场景 |
---|---|---|
Fuel | 简洁、Kotlin 友好 | 快速原型、小型项目 |
Ktor | 异步、DSL 风格优美 | 全栈 Kotlin 项目、协程环境 |
OkHttp | 功能全面、稳定可靠 | 复杂请求控制、Android 主力 |
Retrofit | 类型安全、接口驱动 | 大型项目、RESTful 接口聚合 |
选择哪个库取决于你的技术栈和项目复杂度:
- 想快速搞定?👉 用 Fuel
- 已经在用 Ktor?👉 原生支持别犹豫
- 做 Android 开发?👉 OkHttp + Retrofit 黄金组合
- 追求类型安全和维护性?👉 Retrofit 是首选
🔗 示例完整代码已托管至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-io
合理利用这些工具,文件上传再也不是难题。