Move rate limit interceptor logic to module (#1100)
This commit is contained in:
parent
2ffb53ddbe
commit
5ea7a40919
|
@ -0,0 +1,29 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
buildToolsVersion '28.0.3'
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 27
|
||||||
|
versionCode 1
|
||||||
|
versionName '1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="eu.kanade.tachiyomi.lib.ratelimit">
|
||||||
|
</manifest>
|
|
@ -0,0 +1,58 @@
|
||||||
|
package eu.kanade.tachiyomi.lib.ratelimit
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OkHttp interceptor that handles rate limiting.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* permits = 5, period = 1, unit = seconds => 5 requests per second
|
||||||
|
* permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes
|
||||||
|
*
|
||||||
|
* @param permits {Int} Number of requests allowed within a period of units.
|
||||||
|
* @param period {Long} The limiting duration. Defaults to 1.
|
||||||
|
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
|
||||||
|
*/
|
||||||
|
class RateLimitInterceptor(
|
||||||
|
private val permits: Int,
|
||||||
|
private val period: Long = 1,
|
||||||
|
private val unit: TimeUnit = TimeUnit.SECONDS) : Interceptor {
|
||||||
|
|
||||||
|
private val requestQueue = ArrayList<Long>(permits)
|
||||||
|
private val rateLimitMillis = unit.toMillis(period)
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
synchronized(requestQueue) {
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
|
val waitTime = if (requestQueue.size < permits) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
val oldestReq = requestQueue[0]
|
||||||
|
val newestReq = requestQueue[permits - 1]
|
||||||
|
|
||||||
|
if (newestReq - oldestReq > rateLimitMillis) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
oldestReq + rateLimitMillis - now // Remaining time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestQueue.size == permits) {
|
||||||
|
requestQueue.removeAt(0)
|
||||||
|
}
|
||||||
|
if (waitTime > 0) {
|
||||||
|
requestQueue.add(now + waitTime)
|
||||||
|
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
|
||||||
|
} else {
|
||||||
|
requestQueue.add(now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain.proceed(chain.request())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,5 +7,7 @@ new File(rootDir, "src").eachDir { dir ->
|
||||||
}
|
}
|
||||||
include ':duktape-stub'
|
include ':duktape-stub'
|
||||||
include ':preference-stub'
|
include ':preference-stub'
|
||||||
|
include ':lib-ratelimit'
|
||||||
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
|
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
|
||||||
project(':preference-stub').projectDir = new File("lib/preference-stub")
|
project(':preference-stub').projectDir = new File("lib/preference-stub")
|
||||||
|
project(':lib-ratelimit').projectDir = new File("lib/ratelimit")
|
||||||
|
|
|
@ -5,11 +5,12 @@ ext {
|
||||||
appName = 'Tachiyomi: MangaDex'
|
appName = 'Tachiyomi: MangaDex'
|
||||||
pkgNameSuffix = 'all.mangadex'
|
pkgNameSuffix = 'all.mangadex'
|
||||||
extClass = '.MangadexFactory'
|
extClass = '.MangadexFactory'
|
||||||
extVersionCode = 56
|
extVersionCode = 57
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':lib-ratelimit')
|
||||||
compileOnly project(':preference-stub')
|
compileOnly project(':preference-stub')
|
||||||
compileOnly 'com.google.code.gson:gson:2.8.2'
|
compileOnly 'com.google.code.gson:gson:2.8.2'
|
||||||
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
|
||||||
|
|
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.SystemClock
|
|
||||||
import android.support.v7.preference.ListPreference
|
import android.support.v7.preference.ListPreference
|
||||||
import android.support.v7.preference.PreferenceScreen
|
import android.support.v7.preference.PreferenceScreen
|
||||||
import com.github.salomonbrys.kotson.forEach
|
import com.github.salomonbrys.kotson.forEach
|
||||||
|
@ -16,6 +15,7 @@ import com.github.salomonbrys.kotson.string
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
|
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
@ -28,7 +28,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -57,37 +56,7 @@ open class Mangadex(override val lang: String, private val internalLang: String,
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val requestsPerSecond = 4
|
private val rateLimitInterceptor = RateLimitInterceptor(4)
|
||||||
private val lastRequests = ArrayList<Long>(requestsPerSecond)
|
|
||||||
private val rateLimitInterceptor = Interceptor {
|
|
||||||
synchronized(this) {
|
|
||||||
val now = SystemClock.elapsedRealtime()
|
|
||||||
val waitTime = if (lastRequests.size < requestsPerSecond) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
val oldestReq = lastRequests[0]
|
|
||||||
val newestReq = lastRequests[requestsPerSecond - 1]
|
|
||||||
|
|
||||||
if (newestReq - oldestReq > 1000) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
oldestReq + 1000 - now // Remaining time for the next second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastRequests.size == requestsPerSecond) {
|
|
||||||
lastRequests.removeAt(0)
|
|
||||||
}
|
|
||||||
if (waitTime > 0) {
|
|
||||||
lastRequests.add(now + waitTime)
|
|
||||||
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
|
|
||||||
} else {
|
|
||||||
lastRequests.add(now)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it.proceed(it.request())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addNetworkInterceptor(rateLimitInterceptor)
|
.addNetworkInterceptor(rateLimitInterceptor)
|
||||||
|
|
Loading…
Reference in New Issue