-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add and detect AbuseRateLimitError. #441
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".