diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e28409cec..64ffa63f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ * `MessageUpdate` now is implementing `BaseSentMessageUpdate` interface * `UpdatesPoller` and all its usages, childs and childs usages now are deprecated * `GetUpdates#timeout` type now is `Seconds` (in fact it is `Int` as previously) + * `KtorRequestsExecutor` now is using a copy of incoming `HttpClient` object and install `HttpTimeout` feature + * `AbstractRequestCallFactory` now setting up a custom delay in case if request is `GetUpdates` * `TelegramBotAPI-extensions-api`: * All functions from `com.github.insanusmokrassar.TelegramBotAPI.utils.extensions.UpdatesPolling` now available in package `com.github.insanusmokrassar.TelegramBotAPI.extensions.api.updates.UpdatesPolling` diff --git a/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt index f87a5cfa63..6624211c2b 100644 --- a/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt +++ b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/updates/UpdatesPolling.kt @@ -1,6 +1,7 @@ package com.github.insanusmokrassar.TelegramBotAPI.extensions.api.updates import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException import com.github.insanusmokrassar.TelegramBotAPI.extensions.api.InternalUtils.convertWithMediaGroupUpdates import com.github.insanusmokrassar.TelegramBotAPI.extensions.api.InternalUtils.lastUpdateIdentifier import com.github.insanusmokrassar.TelegramBotAPI.extensions.api.getUpdates @@ -10,10 +11,11 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.* +import io.ktor.client.features.HttpRequestTimeoutException import kotlinx.coroutines.* fun RequestsExecutor.startGettingOfUpdates( - timeoutMillis: Seconds = 30, + timeoutSeconds: Seconds = 30, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), allowedUpdates: List? = null, updatesReceiver: UpdateReceiver @@ -21,30 +23,37 @@ fun RequestsExecutor.startGettingOfUpdates( var lastUpdateIdentifier: UpdateIdentifier? = null while (isActive) { - supervisorScope { - val updates = getUpdates( - offset = lastUpdateIdentifier, - timeout = timeoutMillis, - allowed_updates = allowedUpdates - ).convertWithMediaGroupUpdates() - + try { supervisorScope { - for (update in updates) { - updatesReceiver(update) + val updates = getUpdates( + offset = lastUpdateIdentifier?.plus(1), + timeout = timeoutSeconds, + allowed_updates = allowedUpdates + ).convertWithMediaGroupUpdates() - lastUpdateIdentifier = update.lastUpdateIdentifier() + supervisorScope { + for (update in updates) { + updatesReceiver(update) + + lastUpdateIdentifier = update.lastUpdateIdentifier() + } } } + } catch (e: HttpRequestTimeoutException) { + e // it is ok due to mechanism of long polling + } catch (e: RequestException) { + e // it is not ok, but in most cases it will mean that there is some limit for requests count + delay(1000L) } } } fun RequestsExecutor.startGettingOfUpdates( updatesFilter: UpdatesFilter, - timeoutMillis: Seconds = 30, + timeoutSeconds: Seconds = 30, scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ): Job = startGettingOfUpdates( - timeoutMillis, + timeoutSeconds, scope, updatesFilter.allowedUpdates, updatesFilter.asUpdateReceiver @@ -66,7 +75,7 @@ fun RequestsExecutor.startGettingOfUpdates( preCheckoutQueryCallback: UpdateReceiver? = null, pollCallback: UpdateReceiver? = null, pollAnswerCallback: UpdateReceiver? = null, - timeoutMillis: Seconds = 30, + timeoutSeconds: Seconds = 30, scope: CoroutineScope = GlobalScope ): Job { return startGettingOfUpdates( @@ -87,7 +96,7 @@ fun RequestsExecutor.startGettingOfUpdates( pollCallback, pollAnswerCallback ), - timeoutMillis, + timeoutSeconds, scope ) } @@ -105,7 +114,7 @@ fun RequestsExecutor.startGettingOfUpdates( preCheckoutQueryCallback: UpdateReceiver? = null, pollCallback: UpdateReceiver? = null, pollAnswerCallback: UpdateReceiver? = null, - timeoutMillis: Seconds = 30, + timeoutSeconds: Seconds = 30, scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ): Job = startGettingOfUpdates( messageCallback = messageCallback, @@ -123,6 +132,6 @@ fun RequestsExecutor.startGettingOfUpdates( preCheckoutQueryCallback = preCheckoutQueryCallback, pollCallback = pollCallback, pollAnswerCallback = pollAnswerCallback, - timeoutMillis = timeoutMillis, + timeoutSeconds = timeoutSeconds, scope = scope ) diff --git a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt index ee184dcf90..08a98207af 100644 --- a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt +++ b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt @@ -13,14 +13,16 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient import io.ktor.client.call.receive import io.ktor.client.features.ClientRequestException +import io.ktor.client.features.HttpTimeout import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.readText import kotlinx.coroutines.delay +import kotlinx.coroutines.supervisorScope import kotlinx.serialization.json.Json class KtorRequestsExecutor( telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, - private val client: HttpClient = HttpClient(), + client: HttpClient = HttpClient(), callsFactories: List = emptyList(), excludeDefaultFactories: Boolean = false, private val requestsLimiter: RequestLimiter = EmptyLimiter, @@ -34,50 +36,59 @@ class KtorRequestsExecutor( } } + private val client = client.config { + install(HttpTimeout) + } + override suspend fun execute(request: Request): T { - return requestsLimiter.limit { - var statement: HttpStatement? = null - for (factory in callsFactories) { - statement = factory.prepareCall( - client, - telegramAPIUrlsKeeper.commonAPIUrl, - request - ) - if (statement != null) { - break + return try { + supervisorScope { + requestsLimiter.limit { + var statement: HttpStatement? = null + for (factory in callsFactories) { + statement = factory.prepareCall( + client, + telegramAPIUrlsKeeper.commonAPIUrl, + request + ) + if (statement != null) { + break + } + } + + val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") + val content = response.receive() + val responseObject = jsonFormatter.parse(Response.serializer(), content) + + (responseObject.result?.let { + jsonFormatter.fromJson(request.resultDeserializer, it) + } ?: responseObject.parameters?.let { + val error = it.error + if (error is RetryAfterError) { + delay(error.leftToRetry) + execute(request) + } else { + null + } + } ?: response.let { + throw newRequestException( + responseObject, + content, + "Can't get result object from $content" + ) + }) } } - try { - val response = statement ?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") - val content = response.receive() - val responseObject = jsonFormatter.parse(Response.serializer(), content) - - (responseObject.result?.let { - jsonFormatter.fromJson(request.resultDeserializer, it) - } ?: responseObject.parameters?.let { - val error = it.error - if (error is RetryAfterError) { - delay(error.leftToRetry) - execute(request) - } else { - null - } - } ?: response.let { - throw newRequestException( - responseObject, - content, - "Can't get result object from $content" - ) - }) - } catch (e: ClientRequestException) { - val content = e.response.readText() - val responseObject = jsonFormatter.parse(Response.serializer(), content) - throw newRequestException( - responseObject, - content, - "Can't get result object from $content" - ) - } + } catch (e: ClientRequestException) { + val content = e.response.readText() + val responseObject = jsonFormatter.parse(Response.serializer(), content) + throw newRequestException( + responseObject, + content, + "Can't get result object from $content" + ) + } catch (e: Exception) { + throw e } } diff --git a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt index 892ca7dc3d..ba63261451 100644 --- a/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt +++ b/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt @@ -1,8 +1,10 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorCallFactory +import com.github.insanusmokrassar.TelegramBotAPI.requests.GetUpdates import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request import io.ktor.client.HttpClient +import io.ktor.client.features.timeout import io.ktor.client.request.* import io.ktor.client.statement.HttpStatement import io.ktor.http.ContentType @@ -28,6 +30,16 @@ abstract class AbstractRequestCallFactory : KtorCallFactory { method = HttpMethod.Post accept(ContentType.Application.Json) + if (request is GetUpdates) { + request.timeout ?.times(1000L) ?.let { customTimeoutMillis -> + if (customTimeoutMillis > 0) { + timeout { + requestTimeoutMillis = customTimeoutMillis + } + } + } + } + body = preparedBody }, client