From 70dbbd50a3ec0a00b18dc7649eb6f1b6372fe39d Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Thu, 21 Jul 2016 13:53:23 -0700 Subject: [PATCH] Implement an abuse handler If too many requests are made within X amount of time (not the traditional hourly rate limit), github may begin returning 403. Then we should wait for a bit to attempt to access the API again. In this case, we parse out the Retry-After field returned and sleep until that (it's usually 60 seconds) --- src/main/java/org/kohsuke/github/GitHub.java | 4 +++- .../org/kohsuke/github/GitHubBuilder.java | 3 ++- .../org/kohsuke/github/RateLimitHandler.java | 21 +++++++++++++++++++ .../java/org/kohsuke/github/Requester.java | 13 ++++++++++-- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 22792a8798..47388c93f2 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -83,6 +83,7 @@ public class GitHub { private final String apiUrl; /*package*/ final RateLimitHandler rateLimitHandler; + /*package*/ final RateLimitHandler abuseLimitHandler; private HttpConnector connector = HttpConnector.DEFAULT; @@ -122,7 +123,7 @@ public class GitHub { * @param connector * HttpConnector to use. Pass null to use default connector. */ - /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException { + /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, RateLimitHandler abuseLimitHandler) throws IOException { if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize this.apiUrl = apiUrl; if (null != connector) this.connector = connector; @@ -140,6 +141,7 @@ public class GitHub { } this.rateLimitHandler = rateLimitHandler; + this.abuseLimitHandler = abuseLimitHandler; if (login==null && encodedAuthorization!=null) login = getMyself().getLogin(); diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 2abe16f9c1..9ff603e199 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -30,6 +30,7 @@ public class GitHubBuilder { private HttpConnector connector; private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; + private RateLimitHandler abuseLimitHandler = RateLimitHandler.ABUSE; public GitHubBuilder() { } @@ -193,6 +194,6 @@ public HttpURLConnection connect(URL url) throws IOException { } public GitHub build() throws IOException { - return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler); + return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler); } } diff --git a/src/main/java/org/kohsuke/github/RateLimitHandler.java b/src/main/java/org/kohsuke/github/RateLimitHandler.java index b00624ce2b..8ff47a5ab4 100644 --- a/src/main/java/org/kohsuke/github/RateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/RateLimitHandler.java @@ -48,6 +48,27 @@ private long parseWaitTime(HttpURLConnection uc) { return Math.max(10000, Long.parseLong(v)*1000 - System.currentTimeMillis()); } }; + + /** + * Wait until the API abuse "wait time" is passed. + */ + public static final RateLimitHandler ABUSE = new RateLimitHandler() { + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + try { + Thread.sleep(parseWaitTime(uc)); + } catch (InterruptedException _) { + throw (InterruptedIOException)new InterruptedIOException().initCause(e); + } + } + + private long parseWaitTime(HttpURLConnection uc) { + String v = uc.getHeaderField("Retry-After"); + if (v==null) return 60 * 1000; // can't tell, return 1 min + + return Math.max(1000, Long.parseLong(v)*1000); + } + }; /** * Fail immediately. diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 62fb78339f..15f1f19afc 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -576,11 +576,20 @@ private InputStream wrapStream(InputStream in) throws IOException { InputStream es = wrapStream(uc.getErrorStream()); try { if (es!=null) { + String errorString = IOUtils.toString(es, "UTF-8"); + // Check to see whether we hit a 403, and the message indicates the + // abuse handler (occurs on too many concurrent requests) + if (responseCode == HttpURLConnection.HTTP_FORBIDDEN && + errorString.contains("You have triggered an abuse detection mechanism.")) { + this.root.abuseLimitHandler.onError(e,uc); + return; + } + if (e instanceof FileNotFoundException) { // pass through 404 Not Found to allow the caller to handle it intelligently - throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e); + throw (IOException) new FileNotFoundException(errorString).initCause(e); } else - throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e); + throw (IOException) new IOException(errorString).initCause(e); } else throw e; } finally {