improvements in ktor client-server files handling

This commit is contained in:
InsanusMokrassar 2022-11-27 14:38:35 +06:00
parent 30d4507f54
commit 0c5e2862ca
5 changed files with 129 additions and 38 deletions

View File

@ -5,6 +5,9 @@
* `Ktor`: * `Ktor`:
* `Server`: * `Server`:
* Small fix in `handleUniUpload` * Small fix in `handleUniUpload`
* `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
* `Client`:
* New extensions on top of `uniUpload`
## 0.14.3 ## 0.14.3

View File

@ -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()
}

View File

@ -1,9 +1,11 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.FileName import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.utils.io.core.Input
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json 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 * @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 * [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 * 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 <T> HttpClient.uniUpload( expect suspend fun <T> HttpClient.uniUpload(
url: String, url: String,
@ -29,3 +33,73 @@ expect suspend fun <T> HttpClient.uniUpload(
stringFormat: StringFormat = Json, stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> } onUpload: OnUploadCallback = { _, _ -> }
): T? ): 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 <T> HttpClient.uniUpload(
url: String,
file: MPPFile,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = 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 <T> HttpClient.uniUpload(
url: String,
info: UniUploadFileInfo,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = 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 <T> HttpClient.uniUpload(
url: String,
fileName: FileName,
mimeType: String,
inputAllocator: LambdaInputProvider,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
UniUploadFileInfo(fileName, mimeType, inputAllocator),
resultDeserializer,
additionalData,
headers,
stringFormat,
onUpload
)

View File

@ -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()
}

View File

@ -2,6 +2,7 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.downloadToTempFile
import io.ktor.http.content.* import io.ktor.http.content.*
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.receiveMultipart import io.ktor.server.request.receiveMultipart
@ -37,7 +38,7 @@ suspend fun ApplicationCall.uniloadMultipart(
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {} onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely { ): Input = safely {
var resultInput: Input? = null var resultInput: Input? = null
handleUniUpload( handleUniUpload(
@ -59,41 +60,11 @@ suspend fun ApplicationCall.uniloadMultipartFile(
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely { ): MPPFile = safely {
val multipartData = receiveMultipart() uniloadMultipart(
onFormItem,
var resultInput: MPPFile? = null onCustomFileItem,
onBinaryChannelItem,
multipartData.forEachPart { onBinaryContent
when (it) { ).downloadToTempFile()
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")
} }