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)

我们使用 JUnitWireMockRule 模拟服务端行为,确认请求头和内容是否符合预期:

@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

合理利用这些工具,文件上传再也不是难题。


原始标题:File Upload in Kotlin