Ktor踩坑,上传文件同时带参的问题

之前写过一篇Ktor响应请求并接受参数的(点击查看),在该篇文章内讲述了一种上传文件的方法,使用 call.receiveMultipart() 来对上传的文件进行接收。

然而该案例仅有一个文件被上传,对部分同学造成了误导,有人给我提了这样一个问题:

post("/upload") {
    val p = call.receiveParameters()
    val part = call.receiveMultipart()
    val desc = p["desc"] ?: ""
    val file = part.readPart() as? PartData.FileItem
    // save file...
}

用这样的代码来接受一个 post 请求,该请求的形式为:

var fd = new FormData();
fd.append('file', img);
fd.append('desc', desc);
$.ajax({
    url: '/req',
    type: 'POST',
    dataType: 'json',
    data: fd
});

“诶,看起来没错啊”,第一眼看到这代码时,我也觉得似乎是对的,但是实际跑一下,这份代码是会报错的,下面细细说来。

首先,对于一个 post 请求,是要分清楚 Content-Type 的,虽然在请求里没有明说,但是我们应当可以推断出不同的请求会拥有的不同Contentt-Type。对于不带文件上传的请求,其 Content-Type 应当是 application/x-www-form-urlencoded,而对于带文件的,是 multipart/form-data,因此,对于带了文件又带了普通参数的请求,应当使用call.receiveMultipart() 来接收。

那么接下来的问题就容易了,无非是如何取数据的问题,下面给两个函数:

suspend fun MultiPartData.value(name: String) = 
    try { (readAllParts().filter { it.name == name }[0] as? PartData.FormItem)?.value } catch(e: Exception) { null }

suspend fun MultiPartData.file(name: String) = 
    try { readAllParts().filter { it.name == name }[0] as? PartData.FileItem } catch(e: Exception) { null }

然后在收到带文件上传的请求时,就可以这样做了:

post("/upload") {
    val part = call.receiveMultipart()
    val desc = part.value("desc") ?: ""
    val file = part.file("file")
    // save file...
}

推荐阅读更多精彩内容