25
25
26
26
import com .fasterxml .jackson .databind .JsonMappingException ;
27
27
import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
28
- import org .apache .commons .io .IOUtils ;
29
-
30
28
import java .io .FileNotFoundException ;
31
29
import java .io .IOException ;
32
30
import java .io .InputStream ;
42
40
import java .net .URLEncoder ;
43
41
import java .util .ArrayList ;
44
42
import java .util .Collection ;
43
+ import java .util .Date ;
45
44
import java .util .HashMap ;
46
45
import java .util .Iterator ;
47
46
import java .util .LinkedHashMap ;
48
47
import java .util .List ;
49
48
import java .util .Locale ;
50
49
import java .util .Map ;
51
50
import java .util .NoSuchElementException ;
51
+ import java .util .logging .Level ;
52
52
import java .util .logging .Logger ;
53
53
import java .util .regex .Matcher ;
54
54
import java .util .regex .Pattern ;
55
55
import java .util .zip .GZIPInputStream ;
56
-
57
56
import javax .annotation .WillClose ;
57
+ import org .apache .commons .io .IOUtils ;
58
+ import org .apache .commons .lang .StringUtils ;
58
59
59
60
import static java .util .Arrays .asList ;
60
61
import static java .util .logging .Level .FINE ;
61
- import static org .kohsuke .github .GitHub .* ;
62
+ import static org .kohsuke .github .GitHub .MAPPER ;
62
63
63
64
/**
64
65
* 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
281
282
return result ;
282
283
} catch (IOException e ) {
283
284
handleApiError (e );
285
+ } finally {
286
+ noteRateLimit (tailApiUrl );
284
287
}
285
288
}
286
289
}
@@ -299,6 +302,8 @@ public int asHttpStatusCode(String tailApiUrl) throws IOException {
299
302
return uc .getResponseCode ();
300
303
} catch (IOException e ) {
301
304
handleApiError (e );
305
+ } finally {
306
+ noteRateLimit (tailApiUrl );
302
307
}
303
308
}
304
309
}
@@ -313,6 +318,59 @@ public InputStream asStream(String tailApiUrl) throws IOException {
313
318
return wrapStream (uc .getInputStream ());
314
319
} catch (IOException e ) {
315
320
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 );
316
374
}
317
375
}
318
376
}
@@ -382,7 +440,7 @@ private boolean isMethodWithBody() {
382
440
}
383
441
384
442
try {
385
- return new PagingIterator <T >(type , root .getApiURL (s .toString ()));
443
+ return new PagingIterator <T >(type , tailApiUrl , root .getApiURL (s .toString ()));
386
444
} catch (IOException e ) {
387
445
throw new Error (e );
388
446
}
@@ -391,6 +449,7 @@ private boolean isMethodWithBody() {
391
449
class PagingIterator <T > implements Iterator <T > {
392
450
393
451
private final Class <T > type ;
452
+ private final String tailApiUrl ;
394
453
395
454
/**
396
455
* The next batch to be returned from {@link #next()}.
@@ -402,9 +461,10 @@ class PagingIterator<T> implements Iterator<T> {
402
461
*/
403
462
private URL url ;
404
463
405
- PagingIterator (Class <T > type , URL url ) {
406
- this .url = url ;
464
+ PagingIterator (Class <T > type , String tailApiUrl , URL url ) {
407
465
this .type = type ;
466
+ this .tailApiUrl = tailApiUrl ;
467
+ this .url = url ;
408
468
}
409
469
410
470
public boolean hasNext () {
@@ -438,6 +498,8 @@ private void fetch() {
438
498
return ;
439
499
} catch (IOException e ) {
440
500
handleApiError (e );
501
+ } finally {
502
+ noteRateLimit (tailApiUrl );
441
503
}
442
504
}
443
505
} catch (IOException e ) {
0 commit comments