Kotlin Ktor中part.readBytes()在处理multipart请求时如何正确使用?
- 内容介绍
- 相关推荐
本文共计715个文字,预计阅读时间需要3分钟。
在处理《直接对任意PartData调用readBytes()会抛出ClassCastException,因为只有BinaryItem才支持该方法。表格中的文本字段(如name=)需要特别处理。
常见错误现象:java.lang.ClassCastException: class io.ktor.http.content.PartData$FormItem cannot be cast to class io.ktor.http.content.PartData$BinaryItem
- 用
when分支区分处理类型,不要用is硬转 -
BinaryItem对应文件上传;FormItem对应普通字段;Empty可忽略 - 如果只关心文件,跳过非
BinaryItem即可,不用报错中断整个请求
part.readBytes()会把整个文件加载进内存,大文件容易OOM
readBytes()是同步阻塞方法,它会一次性把InputStream读完并返回ByteArray。上传100MB文件就会分配100MB堆内存,Ktor默认不设限制,生产环境极易触发OutOfMemoryError。
- 务必配合
application.conf里ktor.http.maxRequestSize限制总请求大小 - 对单个
BinaryItem,可用part.originalFileName和part.contentType做前置校验(比如拒绝.exe或超5MB的图片) - 真正要存大文件,改用
part.streamProvider()获取InputStream,再流式写入磁盘或对象存储
完整示例:安全读取上传文件并保存到本地
routing { post("/upload") { val multipart = call.receiveMultipart() multipart.forEachPart { part -> when (part) { is PartData.BinaryItem -> { // 1. 校验文件名和大小 val filename = part.originalFileName ?: "unknown" if (filename.endsWith(".zip") && part.getSize() > 10 * 1024 * 1024) { throw BadRequestException("File too large: $filename") } // 2. 同步读取字节(仅适用于小文件) val bytes = part.readBytes() // 3. 写入本地(注意路径安全,别直接拼接originalFileName) val safeName = filename.replace(Regex("[^a-zA-Z0-9._-]"), "_") File("uploads/$safeName").writeBytes(bytes) } is PartData.FormItem -> { println("Form field: ${part.name} = ${part.value}") } else -> part.dispose() } } call.respondText("OK") } }
为什么part.getSize()有时返回-1?
getSize()依赖HTTP头里的Content-Length,但multipart规范不强制客户端发送该字段,尤其浏览器通过<input type="file">上传时经常缺失。此时getSize()返回-1,不能用来判断大小。
- 别用
getSize() == -1当“是大文件”的依据 - 真要限流,得靠
streamProvider()边读边累计字节数,或依赖反向代理(如Nginx)提前拦截超大请求 - Ktor 2.3+ 支持
maxUploadSize配置项,但它是全局的,无法按路由或字段粒度控制
multipart边界解析、字符编码、临时文件清理这些细节藏得深,但实际出问题往往就卡在readBytes()那一行——先判类型,再看大小,最后动文件。
本文共计715个文字,预计阅读时间需要3分钟。
在处理《直接对任意PartData调用readBytes()会抛出ClassCastException,因为只有BinaryItem才支持该方法。表格中的文本字段(如name=)需要特别处理。
常见错误现象:java.lang.ClassCastException: class io.ktor.http.content.PartData$FormItem cannot be cast to class io.ktor.http.content.PartData$BinaryItem
- 用
when分支区分处理类型,不要用is硬转 -
BinaryItem对应文件上传;FormItem对应普通字段;Empty可忽略 - 如果只关心文件,跳过非
BinaryItem即可,不用报错中断整个请求
part.readBytes()会把整个文件加载进内存,大文件容易OOM
readBytes()是同步阻塞方法,它会一次性把InputStream读完并返回ByteArray。上传100MB文件就会分配100MB堆内存,Ktor默认不设限制,生产环境极易触发OutOfMemoryError。
- 务必配合
application.conf里ktor.http.maxRequestSize限制总请求大小 - 对单个
BinaryItem,可用part.originalFileName和part.contentType做前置校验(比如拒绝.exe或超5MB的图片) - 真正要存大文件,改用
part.streamProvider()获取InputStream,再流式写入磁盘或对象存储
完整示例:安全读取上传文件并保存到本地
routing { post("/upload") { val multipart = call.receiveMultipart() multipart.forEachPart { part -> when (part) { is PartData.BinaryItem -> { // 1. 校验文件名和大小 val filename = part.originalFileName ?: "unknown" if (filename.endsWith(".zip") && part.getSize() > 10 * 1024 * 1024) { throw BadRequestException("File too large: $filename") } // 2. 同步读取字节(仅适用于小文件) val bytes = part.readBytes() // 3. 写入本地(注意路径安全,别直接拼接originalFileName) val safeName = filename.replace(Regex("[^a-zA-Z0-9._-]"), "_") File("uploads/$safeName").writeBytes(bytes) } is PartData.FormItem -> { println("Form field: ${part.name} = ${part.value}") } else -> part.dispose() } } call.respondText("OK") } }
为什么part.getSize()有时返回-1?
getSize()依赖HTTP头里的Content-Length,但multipart规范不强制客户端发送该字段,尤其浏览器通过<input type="file">上传时经常缺失。此时getSize()返回-1,不能用来判断大小。
- 别用
getSize() == -1当“是大文件”的依据 - 真要限流,得靠
streamProvider()边读边累计字节数,或依赖反向代理(如Nginx)提前拦截超大请求 - Ktor 2.3+ 支持
maxUploadSize配置项,但它是全局的,无法按路由或字段粒度控制
multipart边界解析、字符编码、临时文件清理这些细节藏得深,但实际出问题往往就卡在readBytes()那一行——先判类型,再看大小,最后动文件。

