2525
2626import com .fasterxml .jackson .databind .JsonMappingException ;
2727import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
28- import org .apache .commons .io .IOUtils ;
29-
3028import java .io .FileNotFoundException ;
3129import java .io .IOException ;
3230import java .io .InputStream ;
4240import java .net .URLEncoder ;
4341import java .util .ArrayList ;
4442import java .util .Collection ;
43+ import java .util .Date ;
4544import java .util .HashMap ;
4645import java .util .Iterator ;
4746import java .util .LinkedHashMap ;
4847import java .util .List ;
4948import java .util .Locale ;
5049import java .util .Map ;
5150import java .util .NoSuchElementException ;
51+ import java .util .logging .Level ;
5252import java .util .logging .Logger ;
5353import java .util .regex .Matcher ;
5454import java .util .regex .Pattern ;
5555import java .util .zip .GZIPInputStream ;
56-
5756import javax .annotation .WillClose ;
57+ import org .apache .commons .io .IOUtils ;
58+ import org .apache .commons .lang .StringUtils ;
5859
5960import static java .util .Arrays .asList ;
6061import static java .util .logging .Level .FINE ;
61- import static org .kohsuke .github .GitHub .* ;
62+ import static org .kohsuke .github .GitHub .MAPPER ;
6263
6364/**
6465 * A builder pattern for making HTTP call and parsing its output.
@@ -281,6 +282,8 @@ private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOExcepti
281282 return result ;
282283 } catch (IOException e ) {
283284 handleApiError (e );
285+ } finally {
286+ noteRateLimit (tailApiUrl );
284287 }
285288 }
286289 }
@@ -299,6 +302,8 @@ public int asHttpStatusCode(String tailApiUrl) throws IOException {
299302 return uc .getResponseCode ();
300303 } catch (IOException e ) {
301304 handleApiError (e );
305+ } finally {
306+ noteRateLimit (tailApiUrl );
302307 }
303308 }
304309 }
@@ -313,6 +318,59 @@ public InputStream asStream(String tailApiUrl) throws IOException {
313318 return wrapStream (uc .getInputStream ());
314319 } catch (IOException e ) {
315320 handleApiError (e );
321+ } finally {
322+ noteRateLimit (tailApiUrl );
323+ }
324+ }
325+ }
326+
327+ private void noteRateLimit (String tailApiUrl ) {
328+ if ("/rate_limit" .equals (tailApiUrl )) {
329+ // the rate_limit API is "free"
330+ return ;
331+ }
332+ if (tailApiUrl .startsWith ("/search" )) {
333+ // the search API uses a different rate limit
334+ return ;
335+ }
336+ String limit = uc .getHeaderField ("X-RateLimit-Limit" );
337+ if (StringUtils .isBlank (limit )) {
338+ // if we are missing a header, return fast
339+ return ;
340+ }
341+ String remaining = uc .getHeaderField ("X-RateLimit-Remaining" );
342+ if (StringUtils .isBlank (remaining )) {
343+ // if we are missing a header, return fast
344+ return ;
345+ }
346+ String reset = uc .getHeaderField ("X-RateLimit-Reset" );
347+ if (StringUtils .isBlank (reset )) {
348+ // if we are missing a header, return fast
349+ return ;
350+ }
351+ GHRateLimit observed = new GHRateLimit ();
352+ try {
353+ observed .limit = Integer .parseInt (limit );
354+ } catch (NumberFormatException e ) {
355+ if (LOGGER .isLoggable (Level .FINEST )) {
356+ LOGGER .log (Level .FINEST , "Malformed X-RateLimit-Limit header value " + limit , e );
357+ }
358+ return ;
359+ }
360+ try {
361+ observed .remaining = Integer .parseInt (remaining );
362+ } catch (NumberFormatException e ) {
363+ if (LOGGER .isLoggable (Level .FINEST )) {
364+ LOGGER .log (Level .FINEST , "Malformed X-RateLimit-Remaining header value " + remaining , e );
365+ }
366+ return ;
367+ }
368+ try {
369+ observed .reset = new Date (Long .parseLong (reset )); // this is madness, storing the date as seconds
370+ root .updateRateLimit (observed );
371+ } catch (NumberFormatException e ) {
372+ if (LOGGER .isLoggable (Level .FINEST )) {
373+ LOGGER .log (Level .FINEST , "Malformed X-RateLimit-Reset header value " + reset , e );
316374 }
317375 }
318376 }
@@ -382,7 +440,7 @@ private boolean isMethodWithBody() {
382440 }
383441
384442 try {
385- return new PagingIterator <T >(type , root .getApiURL (s .toString ()));
443+ return new PagingIterator <T >(type , tailApiUrl , root .getApiURL (s .toString ()));
386444 } catch (IOException e ) {
387445 throw new Error (e );
388446 }
@@ -391,6 +449,7 @@ private boolean isMethodWithBody() {
391449 class PagingIterator <T > implements Iterator <T > {
392450
393451 private final Class <T > type ;
452+ private final String tailApiUrl ;
394453
395454 /**
396455 * The next batch to be returned from {@link #next()}.
@@ -402,9 +461,10 @@ class PagingIterator<T> implements Iterator<T> {
402461 */
403462 private URL url ;
404463
405- PagingIterator (Class <T > type , URL url ) {
406- this .url = url ;
464+ PagingIterator (Class <T > type , String tailApiUrl , URL url ) {
407465 this .type = type ;
466+ this .tailApiUrl = tailApiUrl ;
467+ this .url = url ;
408468 }
409469
410470 public boolean hasNext () {
@@ -438,6 +498,8 @@ private void fetch() {
438498 return ;
439499 } catch (IOException e ) {
440500 handleApiError (e );
501+ } finally {
502+ noteRateLimit (tailApiUrl );
441503 }
442504 }
443505 } catch (IOException e ) {
0 commit comments