feat(http): handle 429 with Retry-After using seconds
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Johannes Frohnmeyer 2023-07-28 16:30:09 +02:00
parent 4df45c8fad
commit 1c5e9ee1b9
Signed by: Johannes
GPG Key ID: E76429612C2929F4

View File

@ -64,6 +64,9 @@ public class HttpUtils {
private final HttpRequest.Builder builder;
private Method method;
private int sent = 0;
private int retryAfterDefault = 5000;
private int retryAfterMax = 15000;
private int retryLimit = 3;
public Request(Method method, String url) throws URISyntaxException {
this.url = url.replace(" ", "%20");
@ -89,6 +92,20 @@ public class HttpUtils {
return this;
}
public Request configureRetryAfter(int defaultDelay, int maxDelay) {
if (defaultDelay < 1) throw new IllegalArgumentException("defaultDelay must be greater than zero");
if (maxDelay < defaultDelay) throw new IllegalArgumentException("maxDelay must be greater than or equal to defaultDelay");
retryAfterDefault = defaultDelay;
retryAfterMax = maxDelay;
return this;
}
public Request setRetryLimit(int limit) {
if (limit < 0) throw new IllegalArgumentException("limit must be greater than or zero");
retryLimit = limit;
return this;
}
public Request userAgent(String value) {
return setHeader("User-Agent", value);
}
@ -135,7 +152,7 @@ public class HttpUtils {
private <T> T _send(String accept, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException {
sent++;
if (sent > 3) throw new IOException("Attempted third redirect, stopping");
if (sent > retryLimit) throw new IOException("Attempted third redirect, stopping");
builder.header("Accept", accept);
if (method != null) builder.method(method.name(), HttpRequest.BodyPublishers.noBody());
if (PROXY_AUTH != null) builder.header("Proxy-Authorization", PROXY_AUTH);
@ -148,26 +165,50 @@ public class HttpUtils {
}
if (res.statusCode() / 100 == 2) return res.body();
Optional<String> location = res.headers().firstValue("location");
// Redirect
if (location.isPresent() && (res.statusCode() == 302 || res.statusCode() == 307) && method == Method.GET) {
Optional<Integer> retryAfter = res.headers().firstValue("Retry-After").flatMap(s -> {
try {
return HttpUtils.get(location.get())._send(accept, responseBodyHandler);
} catch (URISyntaxException e) {
throw new IOException("Could not follow redirect", e);
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
// CurseForge serverside error
if (CURSEFORGE_API.test(url) && res.statusCode() >= 500 && res.statusCode() < 600) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IOException("Could not sleep before resending request" + e);
});
final String exceptionSuffix = " (URL=" + url + ")";
return switch (res.statusCode()) {
case 429 -> {
// Rate limit
yield handleRetryAfter(accept, responseBodyHandler, retryAfter.map(s -> s * 1000).orElse(retryAfterDefault));
}
return _send(accept, responseBodyHandler);
case 302, 307 -> {
// Redirect
if (location.isPresent() && method == Method.GET) {
try {
yield HttpUtils.get(location.get())._send(accept, responseBodyHandler);
} catch (URISyntaxException e) {
throw new IOException("Could not follow redirect" + exceptionSuffix, e);
}
}
throw new IOException("Unexpected redirect: " + res.statusCode() + exceptionSuffix);
}
case 500, 502, 503, 504, 507 -> {
// CurseForge serverside error
if (CURSEFORGE_API.test(url)) {
yield handleRetryAfter(accept, responseBodyHandler, 1000);
}
throw new IOException("Unexpected serverside error: " + res.statusCode() + exceptionSuffix);
}
case 404 -> throw new FileNotFoundException("Didn't find anything under that url" + exceptionSuffix);
default -> throw new IOException("Unexpected return method: " + res.statusCode() + exceptionSuffix);
};
}
private <T> T handleRetryAfter(String accept, HttpResponse.BodyHandler<T> responseBodyHandler, int millis) throws IOException {
if (millis > retryAfterMax) throw new HttpTimeoutException("Wait time specified by Retry-After is too long: " + millis);
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new IOException("Could not sleep before resending request" + e);
}
if (res.statusCode() == 404)
throw new FileNotFoundException("Didn't find anything under that url (URL=" + url + ")");
throw new IOException("Unexpected return method: " + res.statusCode() + " (URL=" + url + ")");
return this._send(accept, responseBodyHandler);
}
public void send() throws IOException {