Kotlin Ktor中part.readBytes()在处理multipart请求时如何正确使用?

2026-04-29 13:243阅读0评论SEO问题
  • 内容介绍
  • 相关推荐

本文共计715个文字,预计阅读时间需要3分钟。

Kotlin Ktor中part.readBytes()在处理multipart请求时如何正确使用?

在处理《直接对任意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.confktor.http.maxRequestSize限制总请求大小
  • 对单个BinaryItem,可用part.originalFileNamepart.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分钟。

Kotlin Ktor中part.readBytes()在处理multipart请求时如何正确使用?

在处理《直接对任意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.confktor.http.maxRequestSize限制总请求大小
  • 对单个BinaryItem,可用part.originalFileNamepart.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()那一行——先判类型,再看大小,最后动文件。