Make RecordingDownloader more resillient against ReCaptchaExceptions

Implement a max number of requests per minute to prevent hitting reate limits and triggering ReCaptchaExceptions. This slows down the RecordingDownloader significantly and can be adjusted if needed. A request ist retried once when facing a ReCaptchaException.
This commit is contained in:
TobiGr 2024-11-17 21:23:24 +01:00
parent 667c867ad8
commit d635d4db2a

View File

@ -15,6 +15,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Random;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -37,17 +38,31 @@ import javax.annotation.Nonnull;
*/ */
class RecordingDownloader extends Downloader { class RecordingDownloader extends Downloader {
public final static String FILE_NAME_PREFIX = "generated_mock_"; public static final String FILE_NAME_PREFIX = "generated_mock_";
// From https://stackoverflow.com/a/15875500/13516981 // From https://stackoverflow.com/a/15875500/13516981
private final static String IP_V4_PATTERN = private static final String IP_V4_PATTERN =
"(?:(?: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]?)"; "(?:(?: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]?)";
private int index = 0; private int index = 0;
private final String path; private final String path;
// try to prevent ReCaptchaExceptions / rate limits by tracking and throttling the requests
/** /**
* Creates the folder described by {@code stringPath} if it does not exists. * The maximum number of requests per 20 seconds which are executed
* by the {@link RecordingDownloader}.
* 20 seconds is used as upper bound because the rate limit can be triggered within 30 seconds
* and hitting the rate limit should be prevented because it comes with a bigger delay.
* The values can be adjusted when executing the downloader and running into problems.
* <p>TODO: Allow adjusting the value by setting a param in the gradle command</p>
*/
private static final int MAX_REQUESTS_PER_20_SECONDS = 30;
private static final long[] requestTimes = new long[MAX_REQUESTS_PER_20_SECONDS];
private static int requestTimesCursor = -1;
private static final Random throttleRandom = new Random();
/**
* Creates the folder described by {@code stringPath} if it does not exist.
* Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}. * Deletes existing files starting with {@link RecordingDownloader#FILE_NAME_PREFIX}.
* @param stringPath Path to the folder where the json files will be saved to. * @param stringPath Path to the folder where the json files will be saved to.
*/ */
@ -69,6 +84,48 @@ class RecordingDownloader extends Downloader {
@Override @Override
public Response execute(@Nonnull final Request request) throws IOException, public Response execute(@Nonnull final Request request) throws IOException,
ReCaptchaException { ReCaptchaException {
// Delay the execution if the max number of requests per minute is reached
final long currentTime = System.currentTimeMillis();
// the cursor points to the latest request time and the next position is the oldest one
final int oldestRequestTimeCursor = (requestTimesCursor + 1) % requestTimes.length;
final long oldestRequestTime = requestTimes[oldestRequestTimeCursor];
if (oldestRequestTime + 20_000 >= currentTime) {
try {
// sleep at least until the oldest request is 20s old, but not more than 20s
final int minSleepTime = (int) (currentTime - oldestRequestTime);
Thread.sleep(minSleepTime + throttleRandom.nextInt(20_000 - minSleepTime));
} catch (InterruptedException e) {
// handle the exception gracefully because it's not critical for the test
System.err.println("Error while throttling the RecordingDownloader.");
e.printStackTrace();
}
}
requestTimesCursor = oldestRequestTimeCursor; // the oldest value needs to be overridden
requestTimes[requestTimesCursor] = System.currentTimeMillis();
// Handle ReCaptchaExceptions by retrying the request once after a while
try {
return executeRequest(request);
} catch (ReCaptchaException e) {
try {
// sleep for 35-60 seconds to circumvent the rate limit
System.out.println("Throttling the RecordingDownloader to handle a ReCaptcha."
+ " Sleeping for 35-60 seconds.");
Thread.sleep(35_000 + throttleRandom.nextInt(25_000));
} catch (InterruptedException ie) {
// handle the exception gracefully because it's not critical for the test
System.err.println("Error while throttling the RecordingDownloader.");
ie.printStackTrace();
e.printStackTrace();
}
return executeRequest(request);
}
}
@Nonnull
private Response executeRequest(@Nonnull final Request request) throws IOException,
ReCaptchaException {
final Downloader downloader = DownloaderTestImpl.getInstance(); final Downloader downloader = DownloaderTestImpl.getInstance();
Response response = downloader.execute(request); Response response = downloader.execute(request);
String cleanedResponseBody = response.responseBody().replaceAll(IP_V4_PATTERN, "127.0.0.1"); String cleanedResponseBody = response.responseBody().replaceAll(IP_V4_PATTERN, "127.0.0.1");