|
| 1 | +package org.schabi.newpipe.downloader.ratelimiting; |
| 2 | + |
| 3 | +import java.io.IOException; |
| 4 | +import java.net.ProtocolException; |
| 5 | +import java.time.Duration; |
| 6 | +import java.util.LinkedHashMap; |
| 7 | +import java.util.Map; |
| 8 | +import java.util.function.Predicate; |
| 9 | + |
| 10 | +import okhttp3.OkHttpClient; |
| 11 | +import okhttp3.Request; |
| 12 | +import okhttp3.Response; |
| 13 | + |
| 14 | +public class RateLimitedClientWrapper { |
| 15 | + |
| 16 | + private static final int REQUEST_RATE_LIMITED_WAIT_MS = 5_000; |
| 17 | + private static final Map<Predicate<String>, RateLimiter> FORCED_RATE_LIMITERS = Map.ofEntries( |
| 18 | + Map.entry(host -> host.endsWith("youtube.com"), |
| 19 | + RateLimiter.create(1.6, Duration.ofSeconds(1), 3.0)) |
| 20 | + ); |
| 21 | + |
| 22 | + private final OkHttpClient client; |
| 23 | + private final Map<String, RateLimiter> hostRateLimiters = new LinkedHashMap<>(); |
| 24 | + |
| 25 | + public RateLimitedClientWrapper(final OkHttpClient client) { |
| 26 | + this.client = client; |
| 27 | + } |
| 28 | + |
| 29 | + protected RateLimiter getRateLimiterFor(final Request request) { |
| 30 | + return hostRateLimiters.computeIfAbsent(request.url().host(), host -> |
| 31 | + FORCED_RATE_LIMITERS.entrySet() |
| 32 | + .stream() |
| 33 | + .filter(e -> e.getKey().test(host)) |
| 34 | + .findFirst() |
| 35 | + .map(Map.Entry::getValue) |
| 36 | + .orElseGet(() -> |
| 37 | + // Default rate limiter per domain |
| 38 | + RateLimiter.create(5, Duration.ofSeconds(5), 3.0))); |
| 39 | + } |
| 40 | + |
| 41 | + public Response executeRequestWithLimit(final Request request) throws IOException { |
| 42 | + Exception cause = null; |
| 43 | + for (int tries = 1; tries <= 3; tries++) { |
| 44 | + try { |
| 45 | + final double rateLimitedSec = getRateLimiterFor(request).acquire(); |
| 46 | + System.out.println( |
| 47 | + "[RATE-LIMIT] Waited " + rateLimitedSec + "s for " + request.url()); |
| 48 | + |
| 49 | + final Response response = client.newCall(request).execute(); |
| 50 | + if(response.code() != 429) { // 429 = Too many requests |
| 51 | + return response; |
| 52 | + } |
| 53 | + cause = new IllegalStateException("HTTP 429 - Too many requests"); |
| 54 | + } catch (final ProtocolException pre) { |
| 55 | + if (!pre.getMessage().startsWith("Too many follow-up")) { // -> Too many requests |
| 56 | + throw pre; |
| 57 | + } |
| 58 | + cause = pre; |
| 59 | + } |
| 60 | + |
| 61 | + final int waitMs = REQUEST_RATE_LIMITED_WAIT_MS * tries; |
| 62 | + System.out.println( |
| 63 | + "[TOO-MANY-REQUESTS] Waiting " + waitMs + "ms for " + request.url()); |
| 64 | + try { |
| 65 | + Thread.sleep(waitMs); |
| 66 | + } catch (final InterruptedException iex) { |
| 67 | + Thread.currentThread().interrupt(); |
| 68 | + } |
| 69 | + } |
| 70 | + throw new IllegalStateException("Retrying/Rate-limiting for " + request.url() + "failed", cause); |
| 71 | + } |
| 72 | + |
| 73 | + public OkHttpClient getClient() { |
| 74 | + return client; |
| 75 | + } |
| 76 | +} |
0 commit comments