mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-12-22 16:47:13 +00:00
MultipleClientKtorRequestsExecutor, DefaultKtorRequestsExecutor, KtorRequestsExecutor as expect class
This commit is contained in:
parent
2778315aed
commit
cd6f4831ae
2
.github/workflows/packages_publishing.yml
vendored
2
.github/workflows/packages_publishing.yml
vendored
@ -14,6 +14,8 @@ jobs:
|
||||
cat gradle.properties | sed -e "s/^library_version=\([0-9\.]*\)/library_version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
|
||||
rm gradle.properties
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: KotlinSymbolProcessing execution
|
||||
run: ./gradlew ksp
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish to Gitea
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -2,9 +2,17 @@
|
||||
|
||||
## 7.0.2
|
||||
|
||||
_This update brings experimental support of `linuxX64` and `mingwX64` platforms_
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.8.10` -> `1.8.20`
|
||||
* `MicroUtils`: `0.17.5` -> `0.17.6`
|
||||
* `Kotlin`: `1.8.10` -> `1.8.20`
|
||||
* `MicroUtils`: `0.17.5` -> `0.17.6`
|
||||
* `Core`:
|
||||
* New `RequestsExecutor` - `MultipleClientKtorRequestsExecutor`
|
||||
* Old `KtorRequestsExecutor` has been renamed to `DefaultKtorRequestsExecutor`
|
||||
* `KtorRequestsExecutor` now is `expect class`
|
||||
* On `JS` and `JVM` platforms it is `DefaultKtorRequestsExecutor`
|
||||
* On `LinuxX64` and `MinGWX64` platforms it is `MultipleClientKtorRequestsExecutor`
|
||||
|
||||
## 7.0.1
|
||||
|
||||
|
@ -31,6 +31,8 @@ kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref =
|
||||
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
|
||||
ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" }
|
||||
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
|
||||
|
||||
|
@ -48,11 +48,23 @@ kotlin {
|
||||
api libs.javax.activation
|
||||
}
|
||||
}
|
||||
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
api libs.ktor.client.curl
|
||||
}
|
||||
}
|
||||
|
||||
mingwX64Main {
|
||||
dependencies {
|
||||
api libs.ktor.client.winhttp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("kspJvm", project(":tgbotapi.ksp"))
|
||||
add("kspCommonMainMetadata", project(":tgbotapi.ksp"))
|
||||
}
|
||||
|
||||
ksp {
|
||||
|
@ -1,134 +1,25 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.bot.exceptions.*
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.*
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.readText
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
|
||||
import io.ktor.client.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@RiskFeature
|
||||
fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf(
|
||||
SimpleRequestCallFactory(),
|
||||
MultipartRequestCallFactory(),
|
||||
DownloadFileRequestCallFactory,
|
||||
DownloadFileChannelRequestCallFactory
|
||||
)
|
||||
|
||||
class KtorRequestsExecutor(
|
||||
/**
|
||||
* Represents default [BaseRequestsExecutor] working on [Ktor](https://ktor.io) [HttpClient]
|
||||
*
|
||||
* * On JS and JVM platforms it is [dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor]
|
||||
* * On LinuxX64 and MingwX64 it is [dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor]
|
||||
*/
|
||||
expect class KtorRequestsExecutor (
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
if (!excludeDefaultFactories) {
|
||||
this + createTelegramBotDefaultKtorCallRequestsFactories()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private val client = client.config {
|
||||
if (client.pluginOrNull(HttpTimeout) == null) {
|
||||
install(HttpTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return runCatchingSafely {
|
||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||
requestsLimiter.limit(request) {
|
||||
var result: T? = null
|
||||
lateinit var factoryHandledRequest: KtorCallFactory
|
||||
for (potentialFactory in callsFactories) {
|
||||
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||
result = potentialFactory.makeCall(
|
||||
client,
|
||||
telegramAPIUrlsKeeper,
|
||||
request,
|
||||
jsonFormatter
|
||||
)
|
||||
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||
if (result != null) {
|
||||
factoryHandledRequest = potentialFactory
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result ?.let {
|
||||
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||
}
|
||||
}.let {
|
||||
val result = it.exceptionOrNull() ?.let { e ->
|
||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
|
||||
|
||||
when (e) {
|
||||
is ClientRequestException -> {
|
||||
val exceptionResult = runCatchingSafely {
|
||||
val content = e.response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
}
|
||||
exceptionResult.exceptionOrNull() ?.let {
|
||||
CommonBotException(cause = e)
|
||||
} ?: exceptionResult.getOrThrow()
|
||||
}
|
||||
is BotException -> e
|
||||
else -> CommonBotException(cause = e)
|
||||
}
|
||||
} ?.let { Result.failure(it) } ?: it
|
||||
pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
||||
class KtorRequestsExecutorBuilder(
|
||||
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
|
||||
) {
|
||||
var client: HttpClient = HttpClient()
|
||||
var callsFactories: List<KtorCallFactory> = emptyList()
|
||||
var excludeDefaultFactories: Boolean = false
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
|
||||
var jsonFormatter: Json = nonstrictJsonFormat
|
||||
|
||||
fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter)
|
||||
}
|
||||
|
||||
inline fun telegramBot(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build()
|
||||
|
||||
/**
|
||||
* Shortcut for [telegramBot]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun telegramBot(
|
||||
token: String,
|
||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||
testServer: Boolean = false,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||
requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
jsonFormatter: Json = nonstrictJsonFormat,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor
|
||||
|
@ -0,0 +1,54 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.*
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@RiskFeature
|
||||
fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf(
|
||||
SimpleRequestCallFactory(),
|
||||
MultipartRequestCallFactory(),
|
||||
DownloadFileRequestCallFactory,
|
||||
DownloadFileChannelRequestCallFactory
|
||||
)
|
||||
|
||||
class KtorRequestsExecutorBuilder(
|
||||
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
|
||||
) {
|
||||
var client: HttpClient = HttpClient()
|
||||
var callsFactories: List<KtorCallFactory> = emptyList()
|
||||
var excludeDefaultFactories: Boolean = false
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
|
||||
var jsonFormatter: Json = nonstrictJsonFormat
|
||||
|
||||
fun build() = KtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper,
|
||||
client,
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter
|
||||
)
|
||||
}
|
||||
|
||||
inline fun telegramBot(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build()
|
||||
|
||||
/**
|
||||
* Shortcut for [telegramBot]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun telegramBot(
|
||||
token: String,
|
||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||
testServer: Boolean = false,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safelyWithResult
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.requests.GetUpdates
|
||||
@ -56,7 +56,7 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
|
||||
val content = response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
|
||||
return safelyWithResult {
|
||||
return runCatchingSafely {
|
||||
(responseObject.result?.let {
|
||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||
} ?: response.let {
|
||||
|
@ -0,0 +1,100 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.exceptions.BotException
|
||||
import dev.inmo.tgbotapi.bot.exceptions.CommonBotException
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorPipelineStepsHolder
|
||||
import dev.inmo.tgbotapi.bot.ktor.createTelegramBotDefaultKtorCallRequestsFactories
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.statement.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class DefaultKtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
if (!excludeDefaultFactories) {
|
||||
this + createTelegramBotDefaultKtorCallRequestsFactories()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private val client = client.config {
|
||||
if (client.pluginOrNull(HttpTimeout) == null) {
|
||||
install(HttpTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return runCatchingSafely {
|
||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||
requestsLimiter.limit(request) {
|
||||
var result: T? = null
|
||||
lateinit var factoryHandledRequest: KtorCallFactory
|
||||
for (potentialFactory in callsFactories) {
|
||||
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||
result = potentialFactory.makeCall(
|
||||
client,
|
||||
telegramAPIUrlsKeeper,
|
||||
request,
|
||||
jsonFormatter
|
||||
)
|
||||
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||
if (result != null) {
|
||||
factoryHandledRequest = potentialFactory
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result ?.let {
|
||||
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||
}
|
||||
}.let {
|
||||
val result = it.exceptionOrNull() ?.let { e ->
|
||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
|
||||
|
||||
when (e) {
|
||||
is ClientRequestException -> {
|
||||
val exceptionResult = runCatchingSafely {
|
||||
val content = e.response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
}
|
||||
exceptionResult.exceptionOrNull() ?.let {
|
||||
CommonBotException(cause = e)
|
||||
} ?: exceptionResult.getOrThrow()
|
||||
}
|
||||
is BotException -> e
|
||||
else -> CommonBotException(cause = e)
|
||||
}
|
||||
} ?.let { Result.failure(it) } ?: it
|
||||
pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
client.close()
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorPipelineStepsHolder
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import io.ktor.client.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms should return [HttpClient.config] call
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
* On MingwX64 it will create copy with WinHttp engine or throw an exception if engine is different with WinHttp
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64 or non WinHttp-based [HttpClient]
|
||||
* on MingwX64
|
||||
*/
|
||||
internal expect inline fun platformClientCopy(client: HttpClient): HttpClient
|
||||
|
||||
/**
|
||||
* Will use its parameters of constructor to create several [DefaultKtorRequestsExecutor] and use them in [execute]
|
||||
* and [close] operations
|
||||
*
|
||||
* This [BaseRequestsExecutor] has been created for native targets due to their inability of requests paralleling
|
||||
*
|
||||
* Under the hood on each [execute] it will take [DefaultKtorRequestsExecutor] and mark it as busy, execute
|
||||
* [Request], free up taken [DefaultKtorRequestsExecutor] and return (or throw) the result of execution
|
||||
*
|
||||
* @param requestExecutorsCount Amount of [DefaultKtorRequestsExecutor] which will be created and used under the
|
||||
* hood
|
||||
*/
|
||||
class MultipleClientKtorRequestsExecutor (
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
callsFactories: List<KtorCallFactory>,
|
||||
excludeDefaultFactories: Boolean,
|
||||
requestsLimiter: RequestLimiter,
|
||||
jsonFormatter: Json,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder,
|
||||
requestExecutorsCount: Int,
|
||||
clientFactory: () -> HttpClient
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val requestExecutors = (0 until requestExecutorsCount).map {
|
||||
DefaultKtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper,
|
||||
clientFactory(),
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter,
|
||||
pipelineStepsHolder
|
||||
)
|
||||
}.toSet()
|
||||
private val freeClients = MutableStateFlow<Set<DefaultKtorRequestsExecutor>>(requestExecutors)
|
||||
private val clientAllocationMutex = Mutex()
|
||||
private val takerFlow = freeClients.mapNotNull {
|
||||
clientAllocationMutex.withLock {
|
||||
freeClients.value.firstOrNull() ?.also {
|
||||
freeClients.value -= it
|
||||
} ?: return@mapNotNull null
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient,
|
||||
callsFactories: List<KtorCallFactory>,
|
||||
excludeDefaultFactories: Boolean,
|
||||
requestsLimiter: RequestLimiter,
|
||||
jsonFormatter: Json,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder
|
||||
) : this(
|
||||
telegramAPIUrlsKeeper,
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter,
|
||||
pipelineStepsHolder,
|
||||
client.engineConfig.threadsCount,
|
||||
{ platformClientCopy(client) }
|
||||
)
|
||||
|
||||
private suspend fun prepareRequestsExecutor(): DefaultKtorRequestsExecutor {
|
||||
return takerFlow.first()
|
||||
}
|
||||
|
||||
private suspend fun freeRequestsExecutor(client: DefaultKtorRequestsExecutor) {
|
||||
clientAllocationMutex.withLock {
|
||||
freeClients.value += client
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> withRequestExecutor(block: suspend (DefaultKtorRequestsExecutor) -> T): T {
|
||||
val requestsExecutor = prepareRequestsExecutor()
|
||||
val result = runCatchingSafely {
|
||||
block(requestsExecutor)
|
||||
}
|
||||
freeRequestsExecutor(requestsExecutor)
|
||||
return result.getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T = withRequestExecutor {
|
||||
it.execute(request)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
requestExecutors.forEach {
|
||||
it.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,27 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Simple limiter which will lock any request when [TooMuchRequestsExceptions] is thrown and rerun request after lock time
|
||||
* Simple limiter which will lock any request when [TooMuchRequestsException] is thrown and rerun request after lock time
|
||||
*/
|
||||
object ExceptionsOnlyLimiter : RequestLimiter {
|
||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: TooMuchRequestsException) {
|
||||
delay(e.retryAfter.leftToRetry)
|
||||
limit(block)
|
||||
var result: Result<T>? = null
|
||||
while (result == null || result.isFailure) {
|
||||
result = runCatchingSafely {
|
||||
block()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
if (it is TooMuchRequestsException) {
|
||||
delay(it.retryAfter.leftToRetry)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = DefaultKtorRequestsExecutor
|
@ -0,0 +1,15 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms should return [HttpClient.config] call
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
* On MingwX64 it will create copy with WinHttp engine or throw an exception if engine is different with WinHttp
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64 or non WinHttp-based [HttpClient]
|
||||
* on MingwX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = client.config { }
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = DefaultKtorRequestsExecutor
|
@ -0,0 +1,15 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms should return [HttpClient.config] call
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
* On MingwX64 it will create copy with WinHttp engine or throw an exception if engine is different with WinHttp
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64 or non WinHttp-based [HttpClient]
|
||||
* on MingwX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = client.config { }
|
1
tgbotapi.core/src/linuxX64Main/kotlin/PackageInfo.kt
Normal file
1
tgbotapi.core/src/linuxX64Main/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1 @@
|
||||
package dev.inmo.tgbotapi
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = MultipleClientKtorRequestsExecutor
|
@ -0,0 +1,24 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.curl.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
* On MingwX64 it will create copy with WinHttp engine or throw an exception if engine is different with WinHttp
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64 or non WinHttp-based [HttpClient]
|
||||
* on MingwX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = (client.engineConfig as? CurlClientEngineConfig) ?.let {
|
||||
lateinit var config: HttpClientConfig<out CurlClientEngineConfig>
|
||||
client.config {
|
||||
config = this as HttpClientConfig<out CurlClientEngineConfig>
|
||||
}.close()
|
||||
HttpClient(Curl) {
|
||||
this.plusAssign(config)
|
||||
}
|
||||
} ?: throw IllegalArgumentException("On LinuxX64 TelegramBotAPI currently support only Curl Ktor HttpClient engine")
|
1
tgbotapi.core/src/mingwX64Main/kotlin/PackageInfo.kt
Normal file
1
tgbotapi.core/src/mingwX64Main/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1 @@
|
||||
package dev.inmo.tgbotapi
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = MultipleClientKtorRequestsExecutor
|
@ -0,0 +1,24 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.winhttp.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
* On MingwX64 it will create copy with WinHttp engine or throw an exception if engine is different with WinHttp
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64 or non WinHttp-based [HttpClient]
|
||||
* on MingwX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = (client.engineConfig as? WinHttpClientEngineConfig) ?.let { engineConfig ->
|
||||
lateinit var config: HttpClientConfig<out WinHttpClientEngineConfig>
|
||||
client.config {
|
||||
config = this as HttpClientConfig<out WinHttpClientEngineConfig>
|
||||
}.close()
|
||||
HttpClient(WinHttp) {
|
||||
this.plusAssign(config)
|
||||
}
|
||||
} ?: throw IllegalArgumentException("On LinuxX64 TelegramBotAPI currently support only Curl Ktor HttpClient engine")
|
Loading…
Reference in New Issue
Block a user