使用 ktor 检查多部分请求正文中是不是存在所有参数

Posted

技术标签:

【中文标题】使用 ktor 检查多部分请求正文中是不是存在所有参数【英文标题】:check whether all parameter exist or not in multipart request body with ktor使用 ktor 检查多部分请求正文中是否存在所有参数 【发布时间】:2020-10-30 13:40:22 【问题描述】:

我正在尝试用kt​​or创建一个multipart请求,其代码如下,

import com.firstapp.modal.response.SuccessResponse
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.http.content.streamProvider
import io.ktor.locations.Location
import io.ktor.locations.post
import io.ktor.request.isMultipart
import io.ktor.request.receive
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.util.getOrFail
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.IllegalArgumentException

@Location("/uploadVideo/title")
class UploadVideo(val title:String)

fun Route.upload(uploadDir: File) 

    post<UploadVideo> 
        val multipart = call.receiveMultipart()
        var videoFile: File? = null

        // Processes each part of the multipart input content of the user
        multipart.forEachPart  part ->
            when (part) 
                is PartData.FormItem -> 
                    if (part.name != "title")
                        throw IllegalArgumentException("Title parameter not found")
                    //title = part.value
                
                is PartData.FileItem -> 
                    if (part.name != "file")
                        throw IllegalArgumentException("file parameter not found")

                    val ext = File(part.originalFileName).extension
                    val file = File(uploadDir, "upload-$System.currentTimeMillis()-$call.parameters.getOrFail("title").hashCode().$ext")
                    part.streamProvider().use  input -> file.outputStream().buffered().use  output -> input.copyToSuspend(output)  
                    videoFile = file
                
            

            part.dispose()
        

        call.respond(
            HttpStatusCode.OK,
            SuccessResponse(
                videoFile!!,
                HttpStatusCode.OK.value,
                "video file stored"
            )
        )
    



suspend fun InputStream.copyToSuspend(
    out: OutputStream,
    bufferSize: Int = DEFAULT_BUFFER_SIZE,
    yieldSize: Int = 4 * 1024 * 1024,
    dispatcher: CoroutineDispatcher = Dispatchers.IO
): Long 
    return withContext(dispatcher) 
        val buffer = ByteArray(bufferSize)
        var bytesCopied = 0L
        var bytesAfterYield = 0L
        while (true) 
            val bytes = read(buffer).takeIf  it >= 0  ?: break
            out.write(buffer, 0, bytes)
            if (bytesAfterYield >= yieldSize) 
                yield()
                bytesAfterYield %= yieldSize
            
            bytesCopied += bytes
            bytesAfterYield += bytes
        
        return@withContext bytesCopied
    

上面的代码或rest api工作正常,但问题是,我想检查所有参数是否可用,即我想发送附加参数以及格式如下的文件,

class VideoDetail(val type: String, val userId: String, val userName: String)

我在这里举一个例子,我想要什么,即

post("/")  request ->
    val requestParamenter = call.receive<UserInsert>()

这里,无论参数是什么,我们传递的都会自动转换成pojo类,如果我们没有传递,它会抛出异常,

所以,我想用 multipart 实现类似的事情。

【问题讨论】:

【参考方案1】:

最后,我能够对问题进行排序,下面是代码,

 @Location("/uploadVideo/id")
class UploadVideo(val id: Int)

fun Route.upload(uploadDir: File) 

    post<UploadVideo> 

        val multipart = call.receiveMultipart().readAllParts()
        val multiMap = multipart.associateBy  it.name .toMap()
        val data = PersonForm(multiMap)
        println(data)
        
        val ext = File(data.file.originalFileName).extension
        val file = File(uploadDir, "upload-$System.currentTimeMillis()-$data.file.originalFileName")
        data.file.streamProvider()
            .use  input -> file.outputStream().buffered().use  output -> input.copyToSuspend(output)  

        call.respond(
            HttpStatusCode.OK,
            SuccessResponse(
                file,
                HttpStatusCode.OK.value,
                "video file stored"
            )
        )
    


suspend fun InputStream.copyToSuspend(
    out: OutputStream,
    bufferSize: Int = DEFAULT_BUFFER_SIZE,
    yieldSize: Int = 4 * 1024 * 1024,
    dispatcher: CoroutineDispatcher = Dispatchers.IO
): Long 
    return withContext(dispatcher) 
        val buffer = ByteArray(bufferSize)
        var bytesCopied = 0L
        var bytesAfterYield = 0L
        while (true) 
            val bytes = read(buffer).takeIf  it >= 0  ?: break
            out.write(buffer, 0, bytes)
            if (bytesAfterYield >= yieldSize) 
                yield()
                bytesAfterYield %= yieldSize
            
            bytesCopied += bytes
            bytesAfterYield += bytes
        
        return@withContext bytesCopied
    


class PersonForm(map: Map<String?, PartData>) 
    val file: PartData.FileItem by map
    val type: PartData.FormItem by map
    val title: PartData.FormItem by map

    override fun toString() = "$file.originalFileName, $type.value, $title.value"

这种方法的唯一问题是使用地图委托,您必须访问,正确地知道地图中是否存在所有参数,即

 val data = PersonForm(multiMap)
        println(data)

【讨论】:

以上是关于使用 ktor 检查多部分请求正文中是不是存在所有参数的主要内容,如果未能解决你的问题,请参考以下文章

获取 Ktor http 请求的内容

Ktor 原生请求拦截器

AFNetworking 2.0 多部分请求正文空白

Spring Data Rest:如何使用请求正文发送多部分文件

如何在 Ktor 中获取 call.response 的 http 正文?

IMAP 协议是不是支持多部分正文中的二进制文件?