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,43 @@ 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+ long currentTime = System .currentTimeMillis ();
88+ // the cursor points to the latest request time and the next position is the oldest one
89+ int oldestRequestTimeCursor = (requestTimesCursor + 1 ) % 12 ;
90+ long oldestRequestTime = requestTimes [oldestRequestTimeCursor ];
91+ if (oldestRequestTime + 1000 >= currentTime ) {
92+ try {
93+ Thread .sleep (throttleRandom .nextInt ((int ) (currentTime - oldestRequestTime ), 1000 ));
94+ } catch (InterruptedException e ) {
95+ System .err .println ("Error while throttling the RecordingDownloader." );
96+ e .printStackTrace ();
97+ }
98+ }
99+ requestTimesCursor = oldestRequestTimeCursor ; // the oldest value needs to be overridden
100+ requestTimes [requestTimesCursor ] = System .currentTimeMillis ();
101+
102+ // Handle ReCaptchaExceptions by retrying the request once after a while
103+ try {
104+ return executeRequest (request );
105+ } catch (ReCaptchaException e ) {
106+ try {
107+ System .out .println ("Throttling the RecordingDownloader to handle a ReCaptchaException. Sleeping for 35-60 seconds." );
108+ Thread .sleep (throttleRandom .nextInt (35_000 , 60_000 ));
109+ } catch (InterruptedException ie ) {
110+ System .err .println ("Error while throttling the RecordingDownloader "
111+ + "to handle a ReCaptchaException" );
112+ ie .printStackTrace ();
113+ e .printStackTrace ();
114+ }
115+ return executeRequest (request );
116+ }
117+ }
118+
119+ @ Nonnull
120+ private Response executeRequest (@ Nonnull final Request request ) throws IOException ,
121+ ReCaptchaException {
72122 final Downloader downloader = DownloaderTestImpl .getInstance ();
73123 Response response = downloader .execute (request );
74124 String cleanedResponseBody = response .responseBody ().replaceAll (IP_V4_PATTERN , "127.0.0.1" );
0 commit comments