1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-11-27 20:48:44 +00:00

add ExceptionsThrottlerTelegramBotMiddleware

This commit is contained in:
InsanusMokrassar 2024-10-24 22:49:28 +06:00
parent f5529033ec
commit b8a6534b6a
7 changed files with 77 additions and 5 deletions

View File

@ -2,6 +2,10 @@
## 18.2.3 ## 18.2.3
* `Core`:
* Add default middleware `ExceptionsThrottlerTelegramBotMiddleware`
* Make `TelegramBotMiddlewaresPipelinesHandler` to be default `TelegramBotPipelinesHandler`
## 18.2.2 ## 18.2.2
* `Version`: * `Version`:

View File

@ -2,6 +2,7 @@ package dev.inmo.tgbotapi.bot.ktor
import dev.inmo.kslog.common.KSLog import dev.inmo.kslog.common.KSLog
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
import dev.inmo.tgbotapi.bot.ktor.middlewares.TelegramBotMiddlewaresPipelinesHandler
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.requests.abstracts.Request
@ -39,7 +40,7 @@ fun KtorRequestsExecutor(
excludeDefaultFactories: Boolean = false, excludeDefaultFactories: Boolean = false,
requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter, requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
jsonFormatter: Json = nonstrictJsonFormat, jsonFormatter: Json = nonstrictJsonFormat,
pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotPipelinesHandler, pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotMiddlewaresPipelinesHandler(),
logger: KSLog = DefaultKTgBotAPIKSLog, logger: KSLog = DefaultKTgBotAPIKSLog,
) = KtorRequestsExecutor( ) = KtorRequestsExecutor(
telegramAPIUrlsKeeper = telegramAPIUrlsKeeper, telegramAPIUrlsKeeper = telegramAPIUrlsKeeper,

View File

@ -27,7 +27,7 @@ class KtorRequestsExecutorBuilder(
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
var jsonFormatter: Json = nonstrictJsonFormat var jsonFormatter: Json = nonstrictJsonFormat
var logger: KSLog = DefaultKTgBotAPIKSLog var logger: KSLog = DefaultKTgBotAPIKSLog
var pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotPipelinesHandler var pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotMiddlewaresPipelinesHandler()
fun includeMiddlewares(block: TelegramBotMiddlewaresPipelinesHandler.Builder.() -> Unit) { fun includeMiddlewares(block: TelegramBotMiddlewaresPipelinesHandler.Builder.() -> Unit) {
pipelineStepsHolder = TelegramBotMiddlewaresPipelinesHandler.build(block) pipelineStepsHolder = TelegramBotMiddlewaresPipelinesHandler.build(block)

View File

@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.bot.ktor.middlewares package dev.inmo.tgbotapi.bot.ktor.middlewares
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.common.Warning import dev.inmo.micro_utils.common.Warning
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler
@ -22,7 +23,7 @@ import dev.inmo.tgbotapi.requests.abstracts.Request
* Non-null result of lambda will be used as the result of request handling * Non-null result of lambda will be used as the result of request handling
*/ */
@Warning("This API is experimental and subject of changes") @Warning("This API is experimental and subject of changes")
class TelegramBotMiddleware( open class TelegramBotMiddleware(
internal val onRequestException: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null, internal val onRequestException: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null,
internal val onBeforeSearchCallFactory: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Unit)? = null, internal val onBeforeSearchCallFactory: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Unit)? = null,
internal val onBeforeCallFactoryMakeCall: (suspend (request: Request<*>, potentialFactory: KtorCallFactory) -> Unit)? = null, internal val onBeforeCallFactoryMakeCall: (suspend (request: Request<*>, potentialFactory: KtorCallFactory) -> Unit)? = null,
@ -30,6 +31,7 @@ class TelegramBotMiddleware(
internal val onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List<KtorCallFactory>) -> Any?)? = null, internal val onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List<KtorCallFactory>) -> Any?)? = null,
internal val onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Any?)? = null, internal val onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Any?)? = null,
internal val onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List<KtorCallFactory>) -> Result<Any?>?)? = null, internal val onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List<KtorCallFactory>) -> Result<Any?>?)? = null,
val id: String = uuid4().toString()
) : TelegramBotPipelinesHandler { ) : TelegramBotPipelinesHandler {
object ResultAbsence : Throwable() object ResultAbsence : Throwable()
override suspend fun <T : Any> onRequestException(request: Request<T>, t: Throwable): T? { override suspend fun <T : Any> onRequestException(request: Request<T>, t: Throwable): T? {

View File

@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.bot.ktor.middlewares package dev.inmo.tgbotapi.bot.ktor.middlewares
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.common.Warning import dev.inmo.micro_utils.common.Warning
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler
@ -14,6 +15,7 @@ class TelegramBotMiddlewareBuilder {
var onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List<KtorCallFactory>) -> Any?)? = null var onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List<KtorCallFactory>) -> Any?)? = null
var onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Any?)? = null var onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List<KtorCallFactory>) -> Any?)? = null
var onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List<KtorCallFactory>) -> Result<Any?>?)? = null var onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List<KtorCallFactory>) -> Result<Any?>?)? = null
var id: String = uuid4().toString()
/** /**
* Useful way to set [onRequestException] * Useful way to set [onRequestException]
@ -67,7 +69,8 @@ class TelegramBotMiddlewareBuilder {
onAfterCallFactoryMakeCall = onAfterCallFactoryMakeCall, onAfterCallFactoryMakeCall = onAfterCallFactoryMakeCall,
onRequestResultPresented = onRequestResultPresented, onRequestResultPresented = onRequestResultPresented,
onRequestResultAbsent = onRequestResultAbsent, onRequestResultAbsent = onRequestResultAbsent,
onRequestReturnResult = onRequestReturnResult onRequestReturnResult = onRequestReturnResult,
id = id
) )
} }
@ -82,6 +85,7 @@ class TelegramBotMiddlewareBuilder {
onRequestResultPresented = middleware.onRequestResultPresented onRequestResultPresented = middleware.onRequestResultPresented
onRequestResultAbsent = middleware.onRequestResultAbsent onRequestResultAbsent = middleware.onRequestResultAbsent
onRequestReturnResult = middleware.onRequestReturnResult onRequestReturnResult = middleware.onRequestReturnResult
id = middleware.id
additionalSetup() additionalSetup()
}.build() }.build()
} }

View File

@ -3,11 +3,12 @@ package dev.inmo.tgbotapi.bot.ktor.middlewares
import dev.inmo.micro_utils.common.Warning import dev.inmo.micro_utils.common.Warning
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler
import dev.inmo.tgbotapi.bot.ktor.middlewares.builtins.ExceptionsThrottlerTelegramBotMiddleware
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
@Warning("This API is experimental and subject of changes") @Warning("This API is experimental and subject of changes")
class TelegramBotMiddlewaresPipelinesHandler( class TelegramBotMiddlewaresPipelinesHandler(
private val middlewares: List<TelegramBotMiddleware> private val middlewares: List<TelegramBotMiddleware> = listOf(ExceptionsThrottlerTelegramBotMiddleware())
) : TelegramBotPipelinesHandler { ) : TelegramBotPipelinesHandler {
override suspend fun <T : Any> onRequestException(request: Request<T>, t: Throwable): T? { override suspend fun <T : Any> onRequestException(request: Request<T>, t: Throwable): T? {
return middlewares.firstNotNullOfOrNull { return middlewares.firstNotNullOfOrNull {
@ -72,6 +73,7 @@ class TelegramBotMiddlewaresPipelinesHandler(
@Warning("This API is experimental and subject of changes") @Warning("This API is experimental and subject of changes")
class Builder { class Builder {
@Warning("This API is experimental and subject of changes")
val middlewares = mutableListOf<TelegramBotMiddleware>() val middlewares = mutableListOf<TelegramBotMiddleware>()
@Warning("This API is experimental and subject of changes") @Warning("This API is experimental and subject of changes")

View File

@ -0,0 +1,59 @@
package dev.inmo.tgbotapi.bot.ktor.middlewares.builtins
import dev.inmo.tgbotapi.bot.ktor.middlewares.TelegramBotMiddleware
import dev.inmo.tgbotapi.requests.abstracts.Request
import korlibs.time.milliseconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.reflect.KClass
import kotlin.time.Duration
/**
* @see invoke
*/
object ExceptionsThrottlerTelegramBotMiddleware {
const val id: String = "ExceptionsThrottlerTelegramBotMiddleware"
/**
* Creates [TelegramBotMiddleware] and configures it with next parameters:
*
* * [TelegramBotMiddleware.onRequestException] will throttle after exception if exception has happened before
* * [TelegramBotMiddleware.onRequestReturnResult] will clear state of all exceptions happened with the [Request] if its
* handling has been completed successfully
*/
operator fun invoke(
exceptionDurationMultiplier: Float = 2f,
initialExceptionDuration: Duration = 125.milliseconds,
): TelegramBotMiddleware = TelegramBotMiddleware.build {
val exceptionsTimeouts = mutableMapOf<KClass<*>, Duration>()
val latestExceptionsRequestsTypes = mutableMapOf<KClass<*>, MutableSet<KClass<*>>>()
val mutex = Mutex()
onRequestException = onRequestException@{ request, t ->
t ?: return@onRequestException null
val kclass = t::class
val toSleep = mutex.withLock {
val latestDuration = exceptionsTimeouts[kclass]
exceptionsTimeouts[kclass] = latestDuration ?.times(exceptionDurationMultiplier.toDouble()) ?: initialExceptionDuration
latestExceptionsRequestsTypes.getOrPut(request::class) { mutableSetOf() }.add(kclass)
latestDuration
}
toSleep ?.let {
delay(it)
}
null
}
onRequestReturnResult = onRequestReturnResult@{ result, request, _ ->
if (result.isSuccess) {
mutex.withLock {
val exceptionKClass = latestExceptionsRequestsTypes.remove(request::class) ?: return@withLock
exceptionKClass.forEach {
exceptionsTimeouts.remove(it)
}
}
}
null
}
id = ExceptionsThrottlerTelegramBotMiddleware.id
}
}