From 0c5e2862ca7e095f36f17e702e5dc03a912f35d1 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 27 Nov 2022 14:38:35 +0600 Subject: [PATCH] improvements in ktor client-server files handling --- CHANGELOG.md | 3 + .../common/CreateTempFileFromInput.kt | 20 +++++ .../inmo/micro_utils/ktor/client/UniUpload.kt | 74 +++++++++++++++++++ .../ktor/common/CreateTempFileFromInput.kt | 23 ++++++ .../ktor/server/UniloadMultipart.kt | 47 +++--------- 5 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt create mode 100644 ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc9b542477..2ef9de518f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * `Ktor`: * `Server`: * Small fix in `handleUniUpload` + * `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart` + * `Client`: + * New extensions on top of `uniUpload` ## 0.14.3 diff --git a/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt b/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt new file mode 100644 index 00000000000..0cc88afc862 --- /dev/null +++ b/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt @@ -0,0 +1,20 @@ +package dev.inmo.micro_utils.common + +import java.io.File +import java.io.InputStream +import java.util.UUID + +fun InputStream.downloadToTempFile( + fileName: String = UUID.randomUUID().toString(), + fileExtension: String? = ".temp", + folder: File? = null +) = File.createTempFile( + fileName, + fileExtension, + folder +).apply { + outputStream().use { + copyTo(it) + } + deleteOnExit() +} diff --git a/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt b/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt index 6bef13f2f33..38f6cc8aea0 100644 --- a/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt +++ b/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt @@ -1,9 +1,11 @@ package dev.inmo.micro_utils.ktor.client import dev.inmo.micro_utils.common.FileName +import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.ktor.common.LambdaInputProvider import io.ktor.client.HttpClient import io.ktor.http.Headers +import io.ktor.utils.io.core.Input import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.StringFormat import kotlinx.serialization.json.Json @@ -20,6 +22,8 @@ data class UniUploadFileInfo( * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value * in case you wish to pass other source of multipart binary data than regular file + * + * @see dev.inmo.micro_utils.ktor.server.handleUniUpload */ expect suspend fun HttpClient.uniUpload( url: String, @@ -29,3 +33,73 @@ expect suspend fun HttpClient.uniUpload( stringFormat: StringFormat = Json, onUpload: OnUploadCallback = { _, _ -> } ): T? + +/** + * Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server + * + * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile + */ +suspend fun HttpClient.uniUpload( + url: String, + file: MPPFile, + resultDeserializer: DeserializationStrategy, + additionalData: Map = emptyMap(), + headers: Headers = Headers.Empty, + stringFormat: StringFormat = Json, + onUpload: OnUploadCallback = { _, _ -> } +): T? = uniUpload( + url, + additionalData + ("bytes" to file), + resultDeserializer, + headers, + stringFormat, + onUpload +) + +/** + * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server + * + * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile + */ +suspend fun HttpClient.uniUpload( + url: String, + info: UniUploadFileInfo, + resultDeserializer: DeserializationStrategy, + additionalData: Map = emptyMap(), + headers: Headers = Headers.Empty, + stringFormat: StringFormat = Json, + onUpload: OnUploadCallback = { _, _ -> } +): T? = uniUpload( + url, + additionalData + ("bytes" to info), + resultDeserializer, + headers, + stringFormat, + onUpload +) + +/** + * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName], + * [mimeType] and [inputAllocator]) with the server + * + * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile + */ +suspend fun HttpClient.uniUpload( + url: String, + fileName: FileName, + mimeType: String, + inputAllocator: LambdaInputProvider, + resultDeserializer: DeserializationStrategy, + additionalData: Map = emptyMap(), + headers: Headers = Headers.Empty, + stringFormat: StringFormat = Json, + onUpload: OnUploadCallback = { _, _ -> } +): T? = uniUpload( + url, + UniUploadFileInfo(fileName, mimeType, inputAllocator), + resultDeserializer, + additionalData, + headers, + stringFormat, + onUpload +) diff --git a/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt b/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt new file mode 100644 index 00000000000..5c7b97f888f --- /dev/null +++ b/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt @@ -0,0 +1,23 @@ +package dev.inmo.micro_utils.ktor.common + +import io.ktor.utils.io.core.Input +import io.ktor.utils.io.core.copyTo +import io.ktor.utils.io.streams.asOutput +import java.io.File +import java.io.InputStream +import java.util.UUID + +fun Input.downloadToTempFile( + fileName: String = UUID.randomUUID().toString(), + fileExtension: String? = ".temp", + folder: File? = null +) = File.createTempFile( + fileName, + fileExtension, + folder +).apply { + outputStream().use { + copyTo(it.asOutput()) + } + deleteOnExit() +} diff --git a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt index c9c2ebac754..7898092e54d 100644 --- a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt +++ b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.ktor.server import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.coroutines.safely +import dev.inmo.micro_utils.ktor.common.downloadToTempFile import io.ktor.http.content.* import io.ktor.server.application.ApplicationCall import io.ktor.server.request.receiveMultipart @@ -37,7 +38,7 @@ suspend fun ApplicationCall.uniloadMultipart( onCustomFileItem: (PartData.FileItem) -> Unit = {}, onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {} -) = safely { +): Input = safely { var resultInput: Input? = null handleUniUpload( @@ -59,41 +60,11 @@ suspend fun ApplicationCall.uniloadMultipartFile( onCustomFileItem: (PartData.FileItem) -> Unit = {}, onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {}, -) = safely { - val multipartData = receiveMultipart() - - var resultInput: MPPFile? = null - - multipartData.forEachPart { - when (it) { - is PartData.FormItem -> onFormItem(it) - is PartData.FileItem -> { - if (it.name == "bytes") { - val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) - resultInput = MPPFile.createTempFile( - name.nameWithoutExtension.let { - var resultName = it - while (resultName.length < 3) { - resultName += "_" - } - resultName - }, - ".${name.extension}" - ).apply { - outputStream().use { fileStream -> - it.streamProvider().use { - it.copyTo(fileStream) - } - } - } - } else { - onCustomFileItem(it) - } - } - is PartData.BinaryItem -> onBinaryContent(it) - is PartData.BinaryChannelItem -> onBinaryChannelItem(it) - } - } - - resultInput ?: error("Bytes has not been received") +): MPPFile = safely { + uniloadMultipart( + onFormItem, + onCustomFileItem, + onBinaryChannelItem, + onBinaryContent + ).downloadToTempFile() }