mirror of
https://github.com/jlengrand/github-api.git
synced 2026-04-06 08:21:23 +00:00
170 lines
7.5 KiB
Java
170 lines
7.5 KiB
Java
package org.kohsuke.github;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InterruptedIOException;
|
|
import java.util.Objects;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
/**
|
|
* A GitHub API Rate Limit Checker called before each request.
|
|
*
|
|
* <p>
|
|
* GitHub allots a certain number of requests to each user or application per period of time. The number of requests
|
|
* remaining and the time when the number will be reset is returned in the response header and can also be requested
|
|
* using {@link GitHub#getRateLimit()}. The "requests per interval" is referred to as the "rate limit".
|
|
* </p>
|
|
* <p>
|
|
* Different parts of the GitHub API have separate rate limits, but most of REST API uses {@link RateLimitTarget#CORE}.
|
|
* Checking your rate limit using {@link GitHub#getRateLimit()} does not effect your rate limit. GitHub prefers that
|
|
* clients stop before exceeding their rate limit rather than stopping after they exceed it.
|
|
* </p>
|
|
* <p>
|
|
* This class provides the infrastructure for calling the appropriate {@link RateLimitChecker} before each request and
|
|
* retrying than call many times as needed. Each {@link RateLimitChecker} decides whether to wait and for how long. This
|
|
* allows for a wide range of {@link RateLimitChecker} implementations, including complex throttling strategies and
|
|
* polling.
|
|
* </p>
|
|
*/
|
|
class GitHubRateLimitChecker {
|
|
|
|
@Nonnull
|
|
private final RateLimitChecker core;
|
|
|
|
@Nonnull
|
|
private final RateLimitChecker search;
|
|
|
|
@Nonnull
|
|
private final RateLimitChecker graphql;
|
|
|
|
@Nonnull
|
|
private final RateLimitChecker integrationManifest;
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(GitHubRateLimitChecker.class.getName());
|
|
|
|
GitHubRateLimitChecker() {
|
|
this(RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE, RateLimitChecker.NONE);
|
|
}
|
|
|
|
GitHubRateLimitChecker(@Nonnull RateLimitChecker core,
|
|
@Nonnull RateLimitChecker search,
|
|
@Nonnull RateLimitChecker graphql,
|
|
@Nonnull RateLimitChecker integrationManifest) {
|
|
this.core = Objects.requireNonNull(core);
|
|
this.search = Objects.requireNonNull(search);
|
|
this.graphql = Objects.requireNonNull(graphql);
|
|
this.integrationManifest = Objects.requireNonNull(integrationManifest);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@link GitHubRateLimitChecker} with a new checker for a particular target.
|
|
*
|
|
* Only one {@link RateLimitChecker} is allowed per target.
|
|
*
|
|
* @param checker
|
|
* the {@link RateLimitChecker} to apply.
|
|
* @param rateLimitTarget
|
|
* the {@link RateLimitTarget} for this checker. If {@link RateLimitTarget#NONE}, checker will be ignored
|
|
* and no change will be made.
|
|
* @return a new {@link GitHubRateLimitChecker}
|
|
*/
|
|
GitHubRateLimitChecker with(@Nonnull RateLimitChecker checker, @Nonnull RateLimitTarget rateLimitTarget) {
|
|
return new GitHubRateLimitChecker(rateLimitTarget == RateLimitTarget.CORE ? checker : core,
|
|
rateLimitTarget == RateLimitTarget.SEARCH ? checker : search,
|
|
rateLimitTarget == RateLimitTarget.GRAPHQL ? checker : graphql,
|
|
rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST ? checker : integrationManifest);
|
|
}
|
|
|
|
/**
|
|
* Checks whether there is sufficient requests remaining within this client's rate limit quota to make the current
|
|
* request.
|
|
* <p>
|
|
* This method does not do the actual check. Instead it selects the appropriate {@link RateLimitChecker} and
|
|
* {@link GHRateLimit.Record} for the current request's {@link RateLimitTarget}. It then calls
|
|
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)}.
|
|
* </p>
|
|
* <p>
|
|
* It is up to {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} to which decide if the rate limit
|
|
* has been exceeded. If it has, {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} will sleep for as
|
|
* long is it chooses and then return {@code true}. If not, that method will return {@code false}.
|
|
* </p>
|
|
* <p>
|
|
* As long as {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} returns {@code true}, this method
|
|
* will request updated rate limit information and call
|
|
* {@link RateLimitChecker#checkRateLimit(GHRateLimit.Record, long)} again. This looping allows different
|
|
* {@link RateLimitChecker} implementations to apply any number of strategies to controlling the speed at which
|
|
* requests are made.
|
|
* </p>
|
|
* <p>
|
|
* When the {@link RateLimitChecker} returns {@code false} this method will return and the request processing will
|
|
* continue.
|
|
* </p>
|
|
* <p>
|
|
* If the {@link RateLimitChecker} for this the current request's urlPath is {@link RateLimitChecker#NONE} the rate
|
|
* limit is not checked.
|
|
* </p>
|
|
*
|
|
* @param client
|
|
* the {@link GitHubClient} to check
|
|
* @param request
|
|
* the {@link GitHubRequest} to check against
|
|
* @throws IOException
|
|
* if there is an I/O error
|
|
*/
|
|
void checkRateLimit(GitHubClient client, GitHubRequest request) throws IOException {
|
|
RateLimitChecker guard = selectChecker(request.rateLimitTarget());
|
|
if (guard == RateLimitChecker.NONE) {
|
|
return;
|
|
}
|
|
|
|
// For the first rate limit, accept the current limit if a valid one is already present.
|
|
GHRateLimit rateLimit = client.rateLimit(request.rateLimitTarget());
|
|
GHRateLimit.Record rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
|
long waitCount = 0;
|
|
try {
|
|
while (guard.checkRateLimit(rateLimitRecord, waitCount)) {
|
|
waitCount++;
|
|
|
|
// When rate limit is exceeded, sleep for one additional second beyond when the
|
|
// called {@link RateLimitChecker} sleeps.
|
|
// Reset time is only accurate to the second, so adding a one second buffer for safety is a good idea.
|
|
// This also keeps polling clients from querying too often.
|
|
Thread.sleep(1000);
|
|
|
|
// After the first wait, always request a new rate limit from the server.
|
|
rateLimit = client.getRateLimit(request.rateLimitTarget());
|
|
rateLimitRecord = rateLimit.getRecord(request.rateLimitTarget());
|
|
}
|
|
} catch (InterruptedException e) {
|
|
throw (IOException) new InterruptedIOException(e.getMessage()).initCause(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the appropriate {@link RateLimitChecker} for a particular target.
|
|
*
|
|
* Analogous with {@link GHRateLimit#getRecord(RateLimitTarget)}.
|
|
*
|
|
* @param rateLimitTarget
|
|
* the rate limit to check
|
|
* @return the {@link RateLimitChecker} for a particular target
|
|
*/
|
|
@Nonnull
|
|
private RateLimitChecker selectChecker(@Nonnull RateLimitTarget rateLimitTarget) {
|
|
if (rateLimitTarget == RateLimitTarget.NONE) {
|
|
return RateLimitChecker.NONE;
|
|
} else if (rateLimitTarget == RateLimitTarget.CORE) {
|
|
return core;
|
|
} else if (rateLimitTarget == RateLimitTarget.SEARCH) {
|
|
return search;
|
|
} else if (rateLimitTarget == RateLimitTarget.GRAPHQL) {
|
|
return graphql;
|
|
} else if (rateLimitTarget == RateLimitTarget.INTEGRATION_MANIFEST) {
|
|
return integrationManifest;
|
|
} else {
|
|
throw new IllegalArgumentException("Unknown rate limit target: " + rateLimitTarget.toString());
|
|
}
|
|
}
|
|
}
|