mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-25 19:48:43 +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
|
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
|
rm gradle.properties
|
||||||
mv gradle.properties.tmp gradle.properties
|
mv gradle.properties.tmp gradle.properties
|
||||||
|
- name: KotlinSymbolProcessing execution
|
||||||
|
run: ./gradlew ksp
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: Publish to Gitea
|
- name: Publish to Gitea
|
||||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -2,9 +2,17 @@
|
|||||||
|
|
||||||
## 7.0.2
|
## 7.0.2
|
||||||
|
|
||||||
|
_This update brings experimental support of `linuxX64` and `mingwX64` platforms_
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
* `Kotlin`: `1.8.10` -> `1.8.20`
|
* `Kotlin`: `1.8.10` -> `1.8.20`
|
||||||
* `MicroUtils`: `0.17.5` -> `0.17.6`
|
* `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
|
## 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-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", 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 = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||||
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", 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
|
api libs.javax.activation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linuxX64Main {
|
||||||
|
dependencies {
|
||||||
|
api libs.ktor.client.curl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mingwX64Main {
|
||||||
|
dependencies {
|
||||||
|
api libs.ktor.client.winhttp
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
add("kspJvm", project(":tgbotapi.ksp"))
|
add("kspCommonMainMetadata", project(":tgbotapi.ksp"))
|
||||||
}
|
}
|
||||||
|
|
||||||
ksp {
|
ksp {
|
||||||
|
@ -1,134 +1,25 @@
|
|||||||
package dev.inmo.tgbotapi.bot.ktor
|
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.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.ExceptionsOnlyLimiter
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||||
import dev.inmo.tgbotapi.types.Response
|
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
|
||||||
import dev.inmo.tgbotapi.utils.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.HttpClient
|
|
||||||
import io.ktor.client.plugins.*
|
|
||||||
import io.ktor.client.statement.bodyAsText
|
|
||||||
import io.ktor.client.statement.readText
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@RiskFeature
|
/**
|
||||||
fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf(
|
* Represents default [BaseRequestsExecutor] working on [Ktor](https://ktor.io) [HttpClient]
|
||||||
SimpleRequestCallFactory(),
|
*
|
||||||
MultipartRequestCallFactory(),
|
* * On JS and JVM platforms it is [dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor]
|
||||||
DownloadFileRequestCallFactory,
|
* * On LinuxX64 and MingwX64 it is [dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor]
|
||||||
DownloadFileChannelRequestCallFactory
|
*/
|
||||||
)
|
expect class KtorRequestsExecutor (
|
||||||
|
|
||||||
class KtorRequestsExecutor(
|
|
||||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||||
client: HttpClient = HttpClient(),
|
client: HttpClient = HttpClient(),
|
||||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||||
excludeDefaultFactories: Boolean = false,
|
excludeDefaultFactories: Boolean = false,
|
||||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
jsonFormatter: Json = nonstrictJsonFormat,
|
||||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
) : BaseRequestsExecutor
|
||||||
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)
|
|
||||||
|
@ -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
|
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.ktor.KtorCallFactory
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||||
import dev.inmo.tgbotapi.requests.GetUpdates
|
import dev.inmo.tgbotapi.requests.GetUpdates
|
||||||
@ -56,7 +56,7 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
|
|||||||
val content = response.bodyAsText()
|
val content = response.bodyAsText()
|
||||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||||
|
|
||||||
return safelyWithResult {
|
return runCatchingSafely {
|
||||||
(responseObject.result?.let {
|
(responseObject.result?.let {
|
||||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||||
} ?: response.let {
|
} ?: 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
|
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
||||||
import kotlinx.coroutines.delay
|
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 {
|
object ExceptionsOnlyLimiter : RequestLimiter {
|
||||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||||
return try {
|
var result: Result<T>? = null
|
||||||
block()
|
while (result == null || result.isFailure) {
|
||||||
} catch (e: TooMuchRequestsException) {
|
result = runCatchingSafely {
|
||||||
delay(e.retryAfter.leftToRetry)
|
block()
|
||||||
limit(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