-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add and detect AbuseRateLimitError. #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -494,6 +494,24 @@ func (r *RateLimitError) Error() string { | |
r.Response.StatusCode, r.Message, r.Rate.Reset.Time.Sub(time.Now())) | ||
} | ||
|
||
// AbuseRateLimitError occurs when GitHub returns 403 Forbidden response with the | ||
// "documentation_url" field value equal to "https://developer.github.com/v3#abuse-rate-limits". | ||
type AbuseRateLimitError struct { | ||
Response *http.Response // HTTP response that caused this error | ||
Message string `json:"message"` // error message | ||
|
||
// RetryAfter is provided with some abuse rate limit errors. If present, | ||
// it is the amount of time that the client should wait before retrying. | ||
// Otherwise, the client should try again later (after an unspecified amount of time). | ||
RetryAfter *time.Duration | ||
} | ||
|
||
func (r *AbuseRateLimitError) Error() string { | ||
return fmt.Sprintf("%v %v: %d %v", | ||
r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), | ||
r.Response.StatusCode, r.Message) | ||
} | ||
|
||
// sanitizeURL redacts the client_secret parameter from the URL which may be | ||
// exposed to the user, specifically in the ErrorResponse error message. | ||
func sanitizeURL(uri *url.URL) *url.URL { | ||
|
@@ -564,6 +582,20 @@ func CheckResponse(r *http.Response) error { | |
Response: errorResponse.Response, | ||
Message: errorResponse.Message, | ||
} | ||
case r.StatusCode == http.StatusForbidden && errorResponse.DocumentationURL == "https://developer.github.com/v3#abuse-rate-limits": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason I matched based on the documentation_url value rather than a prefix of the message is because this seems more reliable. I know there are multiple different types of abuse rate limit errors, and they have different message text. They may have the same prefix, but I don't know for sure what are all the possible messages. If the documentation_url value is "https://developer.github.com/v3#abuse-rate-limits" (note the "abuse" in the URL, it's not https://developer.github.com/v3/#rate-limiting), then it seems quite reasonable to expect this is an abuse rate limit error rather than something else. If there are improvement suggestions, I'd be glad to hear them. |
||
abuseRateLimitError := &AbuseRateLimitError{ | ||
Response: errorResponse.Response, | ||
Message: errorResponse.Message, | ||
} | ||
if v := r.Header["Retry-After"]; len(v) > 0 { | ||
// According to GitHub support, the "Retry-After" header value will be | ||
// an integer which represents the number of seconds that one should | ||
// wait before resuming making requests. | ||
retryAfterSeconds, _ := strconv.ParseInt(v[0], 10, 64) // Error handling is noop. | ||
retryAfter := time.Duration(retryAfterSeconds) * time.Second | ||
abuseRateLimitError.RetryAfter = &retryAfter | ||
} | ||
return abuseRateLimitError | ||
default: | ||
return errorResponse | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially tried to use a value rather than pointer:
But, I changed it to be a pointer to be able to differentiate between a missing Retry-After header (which means "retry later after an unspecified time") vs a present Retry-After header with value of 0 seconds. I have no evidence/proof that 0 is an impossible value, that's why I couldn't use it as a sentinel to mean "unspecified time". If 0 were to come up in reality, it means "retry right now", which is very close to "retry after 1 second", and very different from "retry after unspecified time".