Add support to kotlin.time APIs in the rate limit interceptor (#9797)

* Add support to kotlin.time APIs in the rate limit interceptor.

* Add a missing line break in the doc.

* Move the specific host to the same file.

* Add kotlin.time rule to Proguard and remove specific host rule.

* Mark the old version as deprecated and address review.

* Remove unused import.

* Remove yet another unused import.
This commit is contained in:
Alessandro Jean 2023-08-04 18:11:43 -03:00 committed by GitHub
parent 7798186c32
commit 9b6567f5e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 14 deletions

View file

@ -8,6 +8,7 @@
-keep,allowoptimization class kotlin.** { public protected *; } -keep,allowoptimization class kotlin.** { public protected *; }
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; } -keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
-keep,allowoptimization class kotlinx.serialization.** { public protected *; } -keep,allowoptimization class kotlinx.serialization.** { public protected *; }
-keep,allowoptimization class kotlin.time.** { public protected *; }
-keep,allowoptimization class okhttp3.** { public protected *; } -keep,allowoptimization class okhttp3.** { public protected *; }
-keep,allowoptimization class okio.** { public protected *; } -keep,allowoptimization class okio.** { public protected *; }
-keep,allowoptimization class rx.** { public protected *; } -keep,allowoptimization class rx.** { public protected *; }

View file

@ -27,7 +27,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Calendar import java.util.Calendar
import java.util.concurrent.TimeUnit import kotlin.time.Duration.Companion.minutes
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
@ -35,7 +35,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder() private val authClient = client.newBuilder()
.addInterceptor(interceptor) .addInterceptor(interceptor)
.rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES) .rateLimit(permits = 85, period = 1.minutes)
.build() .build()
suspend fun addLibManga(track: Track): Track { suspend fun addLibManga(track: Track): Track {

View file

@ -8,10 +8,17 @@ import java.io.IOException
import java.util.ArrayDeque import java.util.ArrayDeque
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toDuration
import kotlin.time.toDurationUnit
/** /**
* An OkHttp interceptor that handles rate limiting. * An OkHttp interceptor that handles rate limiting.
* *
* This uses `java.time` APIs and is the legacy method, kept
* for compatibility reasons with existing extensions.
*
* Examples: * Examples:
* *
* permits = 5, period = 1, unit = seconds => 5 requests per second * permits = 5, period = 1, unit = seconds => 5 requests per second
@ -19,27 +26,43 @@ import java.util.concurrent.TimeUnit
* *
* @since extension-lib 1.3 * @since extension-lib 1.3
* *
* @param permits {Int} Number of requests allowed within a period of units. * @param permits [Int] Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1. * @param period [Long] The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. * @param unit [TimeUnit] The unit of time for the period. Defaults to seconds.
*/ */
@Deprecated("Use the version with kotlin.time APIs instead.")
fun OkHttpClient.Builder.rateLimit( fun OkHttpClient.Builder.rateLimit(
permits: Int, permits: Int,
period: Long = 1, period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS, unit: TimeUnit = TimeUnit.SECONDS,
) = addInterceptor(RateLimitInterceptor(null, permits, period, unit)) ) = addInterceptor(RateLimitInterceptor(null, permits, period.toDuration(unit.toDurationUnit())))
/**
* An OkHttp interceptor that handles rate limiting.
*
* Examples:
*
* permits = 5, period = 1.seconds => 5 requests per second
* permits = 10, period = 2.minutes => 10 requests per 2 minutes
*
* @since extension-lib 1.5
*
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimit(permits: Int, period: Duration = 1.seconds) =
addInterceptor(RateLimitInterceptor(null, permits, period))
/** We can probably accept domains or wildcards by comparing with [endsWith], etc. */ /** We can probably accept domains or wildcards by comparing with [endsWith], etc. */
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
internal class RateLimitInterceptor( internal class RateLimitInterceptor(
private val host: String?, private val host: String?,
private val permits: Int, private val permits: Int,
period: Long, period: Duration,
unit: TimeUnit,
) : Interceptor { ) : Interceptor {
private val requestQueue = ArrayDeque<Long>(permits) private val requestQueue = ArrayDeque<Long>(permits)
private val rateLimitMillis = unit.toMillis(period) private val rateLimitMillis = period.inWholeMilliseconds
private val fairLock = Semaphore(1, true) private val fairLock = Semaphore(1, true)
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {

View file

@ -1,12 +1,20 @@
package eu.kanade.tachiyomi.network.interceptor package eu.kanade.tachiyomi.network.interceptor
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toDuration
import kotlin.time.toDurationUnit
/** /**
* An OkHttp interceptor that handles given url host's rate limiting. * An OkHttp interceptor that handles given url host's rate limiting.
* *
* This uses Java Time APIs and is the legacy method, kept
* for compatibility reasons with existing extensions.
*
* Examples: * Examples:
* *
* httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com * httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
@ -14,14 +22,55 @@ import java.util.concurrent.TimeUnit
* *
* @since extension-lib 1.3 * @since extension-lib 1.3
* *
* @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host() * @param httpUrl [HttpUrl] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits {Int} Number of requests allowed within a period of units. * @param permits [Int] Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1. * @param period [Long] The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds. * @param unit [TimeUnit] The unit of time for the period. Defaults to seconds.
*/ */
@Deprecated("Use the version with kotlin.time APIs instead.")
fun OkHttpClient.Builder.rateLimitHost( fun OkHttpClient.Builder.rateLimitHost(
httpUrl: HttpUrl, httpUrl: HttpUrl,
permits: Int, permits: Int,
period: Long = 1, period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS, unit: TimeUnit = TimeUnit.SECONDS,
) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period, unit)) ) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period.toDuration(unit.toDurationUnit())))
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* httpUrl = "https://api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1.seconds => 5 requests per second to api.manga.com
* httpUrl = "https://imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @since extension-lib 1.5
*
* @param httpUrl [HttpUrl] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimitHost(
httpUrl: HttpUrl,
permits: Int,
period: Duration = 1.seconds,
) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period))
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* url = "https://api.manga.com", permits = 5, period = 1.seconds => 5 requests per second to api.manga.com
* url = "https://imagecdn.manga.com", permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @since extension-lib 1.5
*
* @param url [String] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimitHost(
url: String,
permits: Int,
period: Duration = 1.seconds,
) = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period))