1515import java .nio .file .Files ;
1616import java .nio .file .Path ;
1717import java .nio .file .Paths ;
18+ import java .util .Random ;
1819
1920import javax .annotation .Nonnull ;
2021
@@ -46,6 +47,18 @@ class RecordingDownloader extends Downloader {
4647 private int index = 0 ;
4748 private final String path ;
4849
50+ // try to prevent ReCaptchaExceptions / rate limits by tracking and throttling the requests
51+ /**
52+ * The maximum number of requests per minutes which are executed
53+ * by the {@link RecordingDownloader}.
54+ * The values can be adjusted when executing the downloader and running into problems.
55+ * <p>TODO: Allow adjusting the value by setting a param in the gradle command</p>
56+ */
57+ private static final int MAX_REQUESTS_PER_MINUTE = 30 ;
58+ private static final long [] requestTimes = new long [MAX_REQUESTS_PER_MINUTE ];
59+ private static int requestTimesCursor = -1 ;
60+ private static final Random throttleRandom = new Random ();
61+
4962 /**
5063 * Creates the folder described by {@code stringPath} if it does not exists.
5164 * Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}.
@@ -69,6 +82,45 @@ public RecordingDownloader(final String stringPath) throws IOException {
6982 @ Override
7083 public Response execute (@ Nonnull final Request request ) throws IOException ,
7184 ReCaptchaException {
85+
86+ // Delay the execution if the max number of requests per minute is reached
87+ final long currentTime = System .currentTimeMillis ();
88+ // the cursor points to the latest request time and the next position is the oldest one
89+ final int oldestRequestTimeCursor = (requestTimesCursor + 1 ) % 12 ;
90+ final long oldestRequestTime = requestTimes [oldestRequestTimeCursor ];
91+ if (oldestRequestTime + 60_000 >= currentTime ) {
92+ try {
93+ // sleep at least until the oldest request is 60s old, but not more than 60s
94+ final int minSleepTime = (int ) (currentTime - oldestRequestTime );
95+ Thread .sleep (minSleepTime + throttleRandom .nextInt (60_000 - minSleepTime ));
96+ } catch (InterruptedException e ) {
97+ System .err .println ("Error while throttling the RecordingDownloader." );
98+ e .printStackTrace ();
99+ }
100+ }
101+ requestTimesCursor = oldestRequestTimeCursor ; // the oldest value needs to be overridden
102+ requestTimes [requestTimesCursor ] = System .currentTimeMillis ();
103+
104+ // Handle ReCaptchaExceptions by retrying the request once after a while
105+ try {
106+ return executeRequest (request );
107+ } catch (ReCaptchaException e ) {
108+ try {
109+ System .out .println ("Throttling the RecordingDownloader to handle a ReCaptchaException. Sleeping for 35-60 seconds." );
110+ Thread .sleep (35_000 + throttleRandom .nextInt (25_000 ));
111+ } catch (InterruptedException ie ) {
112+ System .err .println ("Error while throttling the RecordingDownloader "
113+ + "to handle a ReCaptchaException" );
114+ ie .printStackTrace ();
115+ e .printStackTrace ();
116+ }
117+ return executeRequest (request );
118+ }
119+ }
120+
121+ @ Nonnull
122+ private Response executeRequest (@ Nonnull final Request request ) throws IOException ,
123+ ReCaptchaException {
72124 final Downloader downloader = DownloaderTestImpl .getInstance ();
73125 Response response = downloader .execute (request );
74126 String cleanedResponseBody = response .responseBody ().replaceAll (IP_V4_PATTERN , "127.0.0.1" );
0 commit comments