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
3738 */
3839class RecordingDownloader extends Downloader {
3940
40- public final static String FILE_NAME_PREFIX = "generated_mock_" ;
41+ public static final String FILE_NAME_PREFIX = "generated_mock_" ;
4142
4243 // From https://stackoverflow.com/a/15875500/13516981
43- private final static String IP_V4_PATTERN =
44+ private static final String IP_V4_PATTERN =
4445 "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\ .){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" ;
4546
4647 private int index = 0 ;
4748 private final String path ;
4849
50+ // try to prevent ReCaptchaExceptions / rate limits by tracking and throttling the requests
4951 /**
50- * Creates the folder described by {@code stringPath} if it does not exists.
52+ * The maximum number of requests per 20 seconds which are executed
53+ * by the {@link RecordingDownloader}.
54+ * 20 seconds is used as upper bound because the rate limit can be triggered within 30 seconds
55+ * and hitting the rate limit should be prevented because it comes with a bigger delay.
56+ * The values can be adjusted when executing the downloader and running into problems.
57+ * <p>TODO: Allow adjusting the value by setting a param in the gradle command</p>
58+ */
59+ private static final int MAX_REQUESTS_PER_20_SECONDS = 30 ;
60+ private static final long [] requestTimes = new long [MAX_REQUESTS_PER_20_SECONDS ];
61+ private static int requestTimesCursor = -1 ;
62+ private static final Random throttleRandom = new Random ();
63+
64+ /**
65+ * Creates the folder described by {@code stringPath} if it does not exist.
5166 * Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}.
5267 * @param stringPath Path to the folder where the json files will be saved to.
5368 */
@@ -69,6 +84,48 @@ public RecordingDownloader(final String stringPath) throws IOException {
6984 @ Override
7085 public Response execute (@ Nonnull final Request request ) throws IOException ,
7186 ReCaptchaException {
87+
88+ // Delay the execution if the max number of requests per minute is reached
89+ final long currentTime = System .currentTimeMillis ();
90+ // the cursor points to the latest request time and the next position is the oldest one
91+ final int oldestRequestTimeCursor = (requestTimesCursor + 1 ) % requestTimes .length ;
92+ final long oldestRequestTime = requestTimes [oldestRequestTimeCursor ];
93+ if (oldestRequestTime + 20_000 >= currentTime ) {
94+ try {
95+ // sleep at least until the oldest request is 20s old, but not more than 20s
96+ final int minSleepTime = (int ) (currentTime - oldestRequestTime );
97+ Thread .sleep (minSleepTime + throttleRandom .nextInt (20_000 - minSleepTime ));
98+ } catch (InterruptedException e ) {
99+ // handle the exception gracefully because it's not critical for the test
100+ System .err .println ("Error while throttling the RecordingDownloader." );
101+ e .printStackTrace ();
102+ }
103+ }
104+ requestTimesCursor = oldestRequestTimeCursor ; // the oldest value needs to be overridden
105+ requestTimes [requestTimesCursor ] = System .currentTimeMillis ();
106+
107+ // Handle ReCaptchaExceptions by retrying the request once after a while
108+ try {
109+ return executeRequest (request );
110+ } catch (ReCaptchaException e ) {
111+ try {
112+ // sleep for 35-60 seconds to circumvent the rate limit
113+ System .out .println ("Throttling the RecordingDownloader to handle a ReCaptcha."
114+ + " Sleeping for 35-60 seconds." );
115+ Thread .sleep (35_000 + throttleRandom .nextInt (25_000 ));
116+ } catch (InterruptedException ie ) {
117+ // handle the exception gracefully because it's not critical for the test
118+ System .err .println ("Error while throttling the RecordingDownloader." );
119+ ie .printStackTrace ();
120+ e .printStackTrace ();
121+ }
122+ return executeRequest (request );
123+ }
124+ }
125+
126+ @ Nonnull
127+ private Response executeRequest (@ Nonnull final Request request ) throws IOException ,
128+ ReCaptchaException {
72129 final Downloader downloader = DownloaderTestImpl .getInstance ();
73130 Response response = downloader .execute (request );
74131 String cleanedResponseBody = response .responseBody ().replaceAll (IP_V4_PATTERN , "127.0.0.1" );
0 commit comments