проверьте, существуют ли все параметры в теле запроса с несколькими частями с помощью ktor
Я пытаюсь создать составной запрос с помощью ktor, код которого выглядит следующим образом:
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)