Move rate limit interceptor logic to module (#1100)

This commit is contained in:
Eugene 2019-05-11 11:30:15 -04:00 committed by GitHub
parent 2ffb53ddbe
commit 5ea7a40919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 35 deletions

View File

@ -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'
}

View File

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.lib.ratelimit">
</manifest>

View File

@ -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())
}
}

View File

@ -7,5 +7,7 @@ new File(rootDir, "src").eachDir { dir ->
}
include ':duktape-stub'
include ':preference-stub'
include ':lib-ratelimit'
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
project(':preference-stub').projectDir = new File("lib/preference-stub")
project(':lib-ratelimit').projectDir = new File("lib/ratelimit")

View File

@ -5,11 +5,12 @@ ext {
appName = 'Tachiyomi: MangaDex'
pkgNameSuffix = 'all.mangadex'
extClass = '.MangadexFactory'
extVersionCode = 56
extVersionCode = 57
libVersion = '1.2'
}
dependencies {
implementation project(':lib-ratelimit')
compileOnly project(':preference-stub')
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex
import android.app.Application
import android.content.SharedPreferences
import android.os.SystemClock
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen
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.JsonObject
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
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 okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
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)
}
private val requestsPerSecond = 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())
}
private val rateLimitInterceptor = RateLimitInterceptor(4)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)