2020-09-22 02:20:22 +00:00
|
|
|
package dev.inmo.micro_utils.ktor.server
|
|
|
|
|
2021-12-14 12:01:41 +00:00
|
|
|
import dev.inmo.micro_utils.common.*
|
2020-09-26 15:42:05 +00:00
|
|
|
import dev.inmo.micro_utils.coroutines.safely
|
|
|
|
import dev.inmo.micro_utils.ktor.common.*
|
2022-03-04 14:16:59 +00:00
|
|
|
import io.ktor.http.*
|
2022-04-27 08:39:21 +00:00
|
|
|
import io.ktor.http.content.*
|
|
|
|
import io.ktor.server.application.ApplicationCall
|
|
|
|
import io.ktor.server.application.call
|
|
|
|
import io.ktor.server.request.receive
|
|
|
|
import io.ktor.server.request.receiveMultipart
|
|
|
|
import io.ktor.server.response.respond
|
|
|
|
import io.ktor.server.response.respondBytes
|
|
|
|
import io.ktor.server.routing.Route
|
|
|
|
import io.ktor.server.websocket.WebSocketServerSession
|
2020-11-27 08:35:00 +00:00
|
|
|
import io.ktor.util.pipeline.PipelineContext
|
2022-04-27 08:39:21 +00:00
|
|
|
import io.ktor.utils.io.core.Input
|
|
|
|
import io.ktor.utils.io.core.use
|
|
|
|
import io.ktor.utils.io.streams.asInput
|
2020-11-27 08:35:00 +00:00
|
|
|
import kotlinx.coroutines.flow.Flow
|
2022-04-27 08:39:21 +00:00
|
|
|
import kotlinx.serialization.DeserializationStrategy
|
|
|
|
import kotlinx.serialization.SerializationStrategy
|
2020-11-27 08:35:00 +00:00
|
|
|
|
|
|
|
class UnifiedRouter(
|
2021-12-14 07:25:58 +00:00
|
|
|
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
|
|
|
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
2020-11-27 08:35:00 +00:00
|
|
|
) {
|
|
|
|
fun <T> Route.includeWebsocketHandling(
|
|
|
|
suburl: String,
|
|
|
|
flow: Flow<T>,
|
2022-03-04 09:31:32 +00:00
|
|
|
serializer: SerializationStrategy<T>,
|
2022-03-04 14:16:59 +00:00
|
|
|
protocol: URLProtocol = URLProtocol.WS,
|
2022-03-04 09:31:32 +00:00
|
|
|
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
2022-03-04 14:16:59 +00:00
|
|
|
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
2020-11-27 08:35:00 +00:00
|
|
|
|
|
|
|
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
|
|
|
answerSerializer: SerializationStrategy<T>,
|
|
|
|
answer: T
|
|
|
|
) {
|
|
|
|
call.respondBytes (
|
|
|
|
serialFormat.encodeDefault(answerSerializer, answer),
|
|
|
|
serialFormatContentType
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
|
|
|
|
deserializer: DeserializationStrategy<T>
|
|
|
|
) = safely {
|
|
|
|
serialFormat.decodeDefault(
|
|
|
|
deserializer,
|
|
|
|
call.receive()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
|
|
|
|
field: String
|
|
|
|
) = call.parameters[field].also {
|
|
|
|
if (it == null) {
|
|
|
|
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
|
|
|
|
field: String
|
|
|
|
) = call.request.queryParameters[field]
|
|
|
|
|
|
|
|
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
|
|
|
|
field: String
|
|
|
|
) = getQueryParameter(field).also {
|
|
|
|
if (it == null) {
|
|
|
|
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
|
|
|
|
field: String,
|
|
|
|
deserializer: DeserializationStrategy<T>
|
|
|
|
) = getQueryParameter(field) ?.let {
|
|
|
|
serialFormat.decodeHex(
|
|
|
|
deserializer,
|
|
|
|
it
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
|
|
|
|
field: String,
|
|
|
|
deserializer: DeserializationStrategy<T>
|
|
|
|
) = decodeUrlQueryValue(field, deserializer).also {
|
|
|
|
if (it == null) {
|
|
|
|
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
|
|
}
|
|
|
|
}
|
2022-03-15 09:33:45 +00:00
|
|
|
|
|
|
|
companion object {
|
2022-03-15 10:30:32 +00:00
|
|
|
val default
|
|
|
|
get() = defaultUnifiedRouter
|
2022-03-15 09:33:45 +00:00
|
|
|
}
|
2020-11-27 08:35:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
val defaultUnifiedRouter = UnifiedRouter()
|
2020-09-22 02:20:22 +00:00
|
|
|
|
|
|
|
suspend fun <T> ApplicationCall.unianswer(
|
|
|
|
answerSerializer: SerializationStrategy<T>,
|
|
|
|
answer: T
|
|
|
|
) {
|
2020-09-26 15:42:05 +00:00
|
|
|
respondBytes (
|
|
|
|
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
|
2020-09-22 02:20:22 +00:00
|
|
|
standardKtorSerialFormatContentType
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> ApplicationCall.uniload(
|
|
|
|
deserializer: DeserializationStrategy<T>
|
2020-09-26 15:42:05 +00:00
|
|
|
) = safely {
|
|
|
|
standardKtorSerialFormat.decodeDefault(
|
|
|
|
deserializer,
|
|
|
|
receive()
|
|
|
|
)
|
|
|
|
}
|
2020-09-22 02:20:22 +00:00
|
|
|
|
2021-12-14 12:01:41 +00:00
|
|
|
suspend fun ApplicationCall.uniloadMultipart(
|
|
|
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
|
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
|
|
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
|
|
|
) = safely {
|
|
|
|
val multipartData = receiveMultipart()
|
|
|
|
|
|
|
|
var resultInput: Input? = null
|
|
|
|
|
|
|
|
multipartData.forEachPart {
|
|
|
|
when (it) {
|
|
|
|
is PartData.FormItem -> onFormItem(it)
|
|
|
|
is PartData.FileItem -> {
|
|
|
|
when (it.name) {
|
2022-04-27 08:39:21 +00:00
|
|
|
"bytes" -> resultInput = it.streamProvider().asInput()
|
2021-12-14 12:01:41 +00:00
|
|
|
else -> onCustomFileItem(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is PartData.BinaryItem -> onBinaryContent(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resultInput ?: error("Bytes has not been received")
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> ApplicationCall.uniloadMultipart(
|
|
|
|
deserializer: DeserializationStrategy<T>,
|
|
|
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
|
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
|
|
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
|
|
|
): Pair<Input, T> {
|
|
|
|
var data: Optional<T>? = null
|
|
|
|
val resultInput = uniloadMultipart(
|
|
|
|
onFormItem,
|
|
|
|
{
|
|
|
|
if (it.name == "data") {
|
2022-04-27 08:39:21 +00:00
|
|
|
data = standardKtorSerialFormat.decodeDefault(deserializer, it.streamProvider().readBytes()).optional
|
2021-12-14 12:01:41 +00:00
|
|
|
} else {
|
|
|
|
onCustomFileItem(it)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onBinaryContent
|
|
|
|
)
|
|
|
|
|
|
|
|
val completeData = data ?: error("Data has not been received")
|
|
|
|
return resultInput to (completeData.dataOrNull().let { it as T })
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
|
|
|
deserializer: DeserializationStrategy<T>,
|
|
|
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
|
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
|
|
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
|
|
|
) = safely {
|
|
|
|
val multipartData = receiveMultipart()
|
|
|
|
|
|
|
|
var resultInput: MPPFile? = null
|
|
|
|
var data: Optional<T>? = null
|
|
|
|
|
|
|
|
multipartData.forEachPart {
|
|
|
|
when (it) {
|
|
|
|
is PartData.FormItem -> onFormItem(it)
|
|
|
|
is PartData.FileItem -> {
|
|
|
|
when (it.name) {
|
|
|
|
"bytes" -> {
|
|
|
|
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
|
|
|
resultInput = MPPFile.createTempFile(
|
2021-12-27 09:55:05 +00:00
|
|
|
name.nameWithoutExtension.let {
|
|
|
|
var resultName = it
|
|
|
|
while (resultName.length < 3) {
|
|
|
|
resultName += "_"
|
|
|
|
}
|
|
|
|
resultName
|
|
|
|
},
|
2021-12-14 12:01:41 +00:00
|
|
|
".${name.extension}"
|
2021-12-14 15:13:11 +00:00
|
|
|
).apply {
|
2021-12-14 16:03:29 +00:00
|
|
|
outputStream().use { fileStream ->
|
2022-04-27 08:39:21 +00:00
|
|
|
it.streamProvider().use {
|
|
|
|
it.copyTo(fileStream)
|
|
|
|
}
|
2021-12-14 16:03:29 +00:00
|
|
|
}
|
2021-12-14 15:13:11 +00:00
|
|
|
}
|
2021-12-14 12:01:41 +00:00
|
|
|
}
|
2022-04-27 08:39:21 +00:00
|
|
|
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.streamProvider().readBytes()).optional
|
2021-12-14 12:01:41 +00:00
|
|
|
else -> onCustomFileItem(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is PartData.BinaryItem -> onBinaryContent(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val completeData = data ?: error("Data has not been received")
|
|
|
|
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun ApplicationCall.uniloadMultipartFile(
|
|
|
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
|
|
|
onCustomFileItem: (PartData.FileItem) -> 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(
|
2021-12-27 09:55:05 +00:00
|
|
|
name.nameWithoutExtension.let {
|
|
|
|
var resultName = it
|
|
|
|
while (resultName.length < 3) {
|
|
|
|
resultName += "_"
|
|
|
|
}
|
|
|
|
resultName
|
|
|
|
},
|
2021-12-14 12:01:41 +00:00
|
|
|
".${name.extension}"
|
2021-12-14 15:13:11 +00:00
|
|
|
).apply {
|
2021-12-14 16:03:29 +00:00
|
|
|
outputStream().use { fileStream ->
|
2022-04-27 08:39:21 +00:00
|
|
|
it.streamProvider().use {
|
|
|
|
it.copyTo(fileStream)
|
|
|
|
}
|
2021-12-14 16:03:29 +00:00
|
|
|
}
|
2021-12-14 15:13:11 +00:00
|
|
|
}
|
2021-12-14 12:01:41 +00:00
|
|
|
} else {
|
|
|
|
onCustomFileItem(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
is PartData.BinaryItem -> onBinaryContent(it)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resultInput ?: error("Bytes has not been received")
|
|
|
|
}
|
|
|
|
|
2020-09-22 02:20:22 +00:00
|
|
|
suspend fun ApplicationCall.getParameterOrSendError(
|
|
|
|
field: String
|
|
|
|
) = parameters[field].also {
|
|
|
|
if (it == null) {
|
|
|
|
respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun ApplicationCall.getQueryParameter(
|
|
|
|
field: String
|
|
|
|
) = request.queryParameters[field]
|
|
|
|
|
|
|
|
suspend fun ApplicationCall.getQueryParameterOrSendError(
|
|
|
|
field: String
|
|
|
|
) = getQueryParameter(field).also {
|
|
|
|
if (it == null) {
|
|
|
|
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun <T> ApplicationCall.decodeUrlQueryValue(
|
|
|
|
field: String,
|
|
|
|
deserializer: DeserializationStrategy<T>
|
|
|
|
) = getQueryParameter(field) ?.let {
|
2020-09-26 15:42:05 +00:00
|
|
|
standardKtorSerialFormat.decodeHex(
|
2020-09-22 02:20:22 +00:00
|
|
|
deserializer,
|
|
|
|
it
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
|
|
|
|
field: String,
|
|
|
|
deserializer: DeserializationStrategy<T>
|
|
|
|
) = decodeUrlQueryValue(field, deserializer).also {
|
|
|
|
if (it == null) {
|
|
|
|
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
|
|
|
}
|
|
|
|
}
|