You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将Retrofit Multipart请求转换为Ktor Client时遇阻求助

问题解决:Retrofit Multipart转Ktor Client实现及日志排查

一、修复Multipart请求实现

你的Ktor代码存在两个关键问题:

  1. 每个文件part的name设置错误(你用了"content",但Retrofit里是"image_$index")
  2. 不必要地多次创建formData并合并,导致请求结构异常

修正后的代码如下:

private suspend fun sendFiles(files: List<File>): HttpResponse {
    return httpClient.post("rest/sendPhotos") {
        setBody(MultiPartFormDataContent(formData {
            files.forEachIndexed { index, file ->
                append(
                    name = "image_$index",
                    value = InputProvider(file.length()) { file.inputStream().asInput() },
                    headers = Headers.build {
                        append(HttpHeaders.ContentType, "multipart/form-items")
                        append(
                            HttpHeaders.ContentDisposition,
                            "form-data; name=\"image_$index\"; filename=\"${file.name}\""
                        )
                    }
                )
            }
        }))
    }
}

或者用Ktor内置的ContentDisposition构建器避免手动拼接字符串出错:

import io.ktor.http.content.*

private suspend fun sendFiles(files: List<File>): HttpResponse {
    return httpClient.post("rest/sendPhotos") {
        setBody(MultiPartFormDataContent(formData {
            files.forEachIndexed { index, file ->
                append(
                    name = "image_$index",
                    value = file.readBytes(),
                    headers = Headers.build {
                        append(HttpHeaders.ContentType, "multipart/form-items")
                        append(
                            HttpHeaders.ContentDisposition,
                            ContentDisposition.File
                                .withParameter(ContentDisposition.Parameters.Name, "image_$index")
                                .withParameter(ContentDisposition.Parameters.FileName, file.name)
                                .toString()
                        )
                    }
                )
            }
        }))
    }
}

说明:

  • 直接在formData DSL中一次性构建所有文件part,保证请求结构和Retrofit完全对齐
  • 每个part的name严格对应Retrofit中的image_$index
  • 用ContentDisposition构建器可避免手动拼接字符串的格式错误

二、解决Ktor日志不显示Multipart请求体的问题

Ktor默认会省略Multipart请求体的日志,可通过以下两种方式解决:

方式1:配置Logging插件打印BODY级日志

确保Ktor Client配置了Logging插件,并设置日志级别为BODY:

import io.ktor.client.plugins.logging.*

val httpClient = HttpClient(OkHttp) {
    install(Logging) {
        level = LogLevel.BODY
        logger = object : Logger {
            override fun log(message: String) {
                // 替换为你的日志输出方式,比如Android Logcat
                println(message)
            }
        }
    }
}

方式2:自定义拦截器手动打印Multipart内容

如果Logging插件仍无法打印,可添加自定义拦截器解析Multipart内容:

import io.ktor.client.plugins.api.*
import io.ktor.http.content.*

val httpClient = HttpClient(OkHttp) {
    install(Plugin) {
        onRequest { request ->
            if (request.body is MultiPartFormDataContent) {
                val multipart = request.body as MultiPartFormDataContent
                println("=== Multipart Request Body ===")
                multipart.parts.forEachIndexed { index, part ->
                    println("Part $index:")
                    println("  Content-Disposition: ${part.headers[HttpHeaders.ContentDisposition]}")
                    println("  Content-Type: ${part.headers[HttpHeaders.ContentType]}")
                    if (part is PartData.FileItem) {
                        println("  File Name: ${part.originalFileName}")
                        println("  File Size: ${part.size}")
                    }
                }
                println("=============================")
            }
        }
    }
}

验证要点

  1. 对比Retrofit日志,确保Ktor请求的Content-Type为multipart/form-data; boundary=xxx
  2. 每个文件part的Content-Disposition包含name="image_$index"和filename="xxx.jpg"
  3. Content-Type保持和Retrofit一致的multipart/form-items

内容的提问来源于stack exchange,提问作者Barmaley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 11:30:11