diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index afbd1ea715..c14f36c04c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ javax-activation = "1.1.1" korlibs = "5.4.0" uuid = "0.8.4" -ktor = "2.3.12" +ktor = "3.0.0" ksp = "2.0.21-1.0.25" kotlin-poet = "1.18.1" diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt index d3f1f94490..df3c7ea281 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt @@ -8,9 +8,7 @@ import dev.inmo.tgbotapi.utils.ByteReadChannelAllocator import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient -import io.ktor.client.call.receive import io.ktor.client.request.get -import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.* import kotlinx.coroutines.* diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt index 2d3a661e7a..593ffcb226 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt @@ -6,9 +6,9 @@ import dev.inmo.tgbotapi.requests.DownloadFile import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.statement.readBytes +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* import kotlinx.serialization.json.Json @RiskFeature @@ -17,13 +17,13 @@ object DownloadFileRequestCallFactory : KtorCallFactory { client: HttpClient, urlsKeeper: TelegramAPIUrlsKeeper, request: Request, - jsonFormatter: Json - ): T? = (request as? DownloadFile) ?.let { + jsonFormatter: Json, + ): T? = (request as? DownloadFile)?.let { val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath) safely { @Suppress("UNCHECKED_CAST") - client.get(fullUrl).readBytes() as T // always ByteArray + client.get(fullUrl).readRawBytes() as T // always ByteArray } } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt index b8d08c10d8..819aa66132 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt @@ -38,7 +38,7 @@ internal expect inline fun platformClientCopy(client: HttpClient): HttpClient * @param requestExecutorsCount Amount of [DefaultKtorRequestsExecutor] which will be created and used under the * hood */ -class MultipleClientKtorRequestsExecutor ( +class MultipleClientKtorRequestsExecutor( telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, callsFactories: List, excludeDefaultFactories: Boolean, @@ -47,7 +47,7 @@ class MultipleClientKtorRequestsExecutor ( pipelineStepsHolder: TelegramBotPipelinesHandler, requestExecutorsCount: Int, logger: KSLog, - clientFactory: () -> HttpClient + clientFactory: () -> HttpClient, ) : BaseRequestsExecutor(telegramAPIUrlsKeeper) { private val requestExecutors = (0 until requestExecutorsCount).map { DefaultKtorRequestsExecutor( @@ -66,7 +66,7 @@ class MultipleClientKtorRequestsExecutor ( private val clientAllocationMutex = Mutex() private val takerFlow = freeClients.mapNotNull { clientAllocationMutex.withLock { - freeClients.value.firstOrNull() ?.also { + freeClients.value.firstOrNull()?.also { freeClients.value -= it } ?: return@mapNotNull null } @@ -81,7 +81,7 @@ class MultipleClientKtorRequestsExecutor ( jsonFormatter: Json, pipelineStepsHolder: TelegramBotPipelinesHandler, logger: KSLog, - diff: Unit + diff: Unit, ) : this( telegramAPIUrlsKeeper, callsFactories, @@ -89,7 +89,7 @@ class MultipleClientKtorRequestsExecutor ( requestsLimiter, jsonFormatter, pipelineStepsHolder, - client.engineConfig.threadsCount, + requestExecutorsCount = 4, // default threads count; configurable through dispatcher property logger, { platformClientCopy(client) } ) diff --git a/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt b/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt index b64c816a97..3d92174aaa 100644 --- a/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt +++ b/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt @@ -1,8 +1,6 @@ package dev.inmo.tgbotapi.utils -import io.ktor.util.toByteArray -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.core.ByteReadPacket -import io.ktor.utils.io.core.Input +import io.ktor.utils.io.* +import io.ktor.utils.io.core.* actual suspend fun ByteReadChannel.asInput(): Input = ByteReadPacket(toByteArray()) diff --git a/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt b/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt index c7ab3b32de..09b2271aae 100644 --- a/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt +++ b/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt @@ -1,19 +1,21 @@ package dev.inmo.tgbotapi.extensions.utils.updates.retrieving -import dev.inmo.micro_utils.coroutines.* +import dev.inmo.micro_utils.coroutines.ExceptionHandler +import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.extensions.utils.nonstrictJsonFormat import dev.inmo.tgbotapi.extensions.utils.updates.flowsUpdatesFilter import dev.inmo.tgbotapi.requests.webhook.SetWebhookRequest import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy -import dev.inmo.tgbotapi.updateshandlers.* +import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter +import dev.inmo.tgbotapi.updateshandlers.UpdateReceiver +import dev.inmo.tgbotapi.updateshandlers.UpdatesFilter import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call +import io.ktor.http.* import io.ktor.server.engine.* -import io.ktor.server.request.receiveText -import io.ktor.server.response.respond +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher @@ -38,7 +40,7 @@ fun Route.includeWebhookHandlingInRoute( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, - block: UpdateReceiver + block: UpdateReceiver, ) { val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block, mediaGroupsDebounceTimeMillis) post { @@ -55,7 +57,7 @@ fun Route.includeWebhookHandlingInRoute( call.respond(HttpStatusCode.InternalServerError) }.getOrThrow() } catch (e: Throwable) { - exceptionsHandler ?.invoke(e) + exceptionsHandler?.invoke(e) } } } @@ -69,7 +71,7 @@ fun Route.includeWebhookHandlingInRouteWithFlows( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, - block: FlowsUpdatesFilter.() -> Unit + block: FlowsUpdatesFilter.() -> Unit, ) = includeWebhookHandlingInRoute( scope, exceptionsHandler, @@ -92,50 +94,57 @@ fun Route.includeWebhookHandlingInRouteWithFlows( * @see UpdatesFilter * @see UpdatesFilter.asUpdateReceiver */ -fun startListenWebhooks( +fun startListenWebhooks( listenPort: Int, - engineFactory: ApplicationEngineFactory<*, *>, + engineFactory: ApplicationEngineFactory, exceptionsHandler: ExceptionHandler, listenHost: String = "0.0.0.0", listenRoute: String? = null, privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, - additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, - block: UpdateReceiver -): ApplicationEngine { - val env = applicationEngineEnvironment { - - module { - routing { - listenRoute ?.also { - createRouteFromPath(it).includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) - } ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) - } - } - - privateKeyConfig ?.let { - sslConnector( - privateKeyConfig.keyStore, - privateKeyConfig.aliasName, - privateKeyConfig::keyStorePassword, - privateKeyConfig::aliasPassword - ) { + additionalApplicationEnvironmentConfigurator: ApplicationEnvironmentBuilder.() -> Unit = {}, + additionalEngineConfigurator: TConfiguration.() -> Unit = {}, + block: UpdateReceiver, +): EmbeddedServer = + embeddedServer( + factory = engineFactory, + environment = applicationEnvironment { + additionalApplicationEnvironmentConfigurator() + }, + configure = { + privateKeyConfig?.let { + sslConnector( + privateKeyConfig.keyStore, + privateKeyConfig.aliasName, + privateKeyConfig::keyStorePassword, + privateKeyConfig::aliasPassword + ) { + host = listenHost + port = listenPort + } + } ?: connector { host = listenHost port = listenPort } - } ?: connector { - host = listenHost - port = listenPort + + additionalEngineConfigurator() + }, + module = { + routing { + listenRoute?.also { + createRouteFromPath(it).includeWebhookHandlingInRoute( + scope, + exceptionsHandler, + mediaGroupsDebounceTimeMillis, + block + ) + } ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) + } } - - additionalApplicationEngineEnvironmentConfigurator() - } - - return embeddedServer(engineFactory, env).also { + ).also { it.start(false) } -} /** * Setting up ktor server, set webhook info via [SetWebhookRequest] request. @@ -152,9 +161,9 @@ fun startListenWebhooks( * @see UpdatesFilter.asUpdateReceiver */ @Suppress("unused") -suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( +suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( listenPort: Int, - engineFactory: ApplicationEngineFactory<*, *>, + engineFactory: ApplicationEngineFactory, setWebhookRequest: SetWebhookRequest, exceptionsHandler: ExceptionHandler = {}, listenHost: String = "0.0.0.0", @@ -162,11 +171,24 @@ suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, - additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, - block: UpdateReceiver -): ApplicationEngine = try { + additionalApplicationEnvironmentConfigurator: ApplicationEnvironmentBuilder.() -> Unit = {}, + additionalEngineConfigurator: TConfiguration.() -> Unit = {}, + block: UpdateReceiver, +): EmbeddedServer = try { execute(setWebhookRequest) - startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, mediaGroupsDebounceTimeMillis, additionalApplicationEngineEnvironmentConfigurator, block) + startListenWebhooks( + listenPort, + engineFactory, + exceptionsHandler, + listenHost, + listenRoute, + privateKeyConfig, + scope, + mediaGroupsDebounceTimeMillis, + additionalApplicationEnvironmentConfigurator, + additionalEngineConfigurator, + block + ) } catch (e: Exception) { throw e }