Skip to content

Commit 77b2d75

Browse files
authored
Don't update the ratelimits if we got a response from a cache (#2273)
1 parent 62eaf97 commit 77b2d75

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

github/github.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,9 +655,13 @@ func (c *Client) BareDo(ctx context.Context, req *http.Request) (*Response, erro
655655

656656
response := newResponse(resp)
657657

658-
c.rateMu.Lock()
659-
c.rateLimits[rateLimitCategory] = response.Rate
660-
c.rateMu.Unlock()
658+
// Don't update the rate limits if this was a cached response.
659+
// X-From-Cache is set by https://github.com/gregjones/httpcache
660+
if response.Header.Get("X-From-Cache") == "" {
661+
c.rateMu.Lock()
662+
c.rateLimits[rateLimitCategory] = response.Rate
663+
c.rateMu.Unlock()
664+
}
661665

662666
err = CheckResponse(resp)
663667
if err != nil {

github/github_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,49 @@ func TestDo_rateLimit_noNetworkCall(t *testing.T) {
11161116
}
11171117
}
11181118

1119+
// Ignore rate limit headers if the response was served from cache.
1120+
func TestDo_rateLimit_ignoredFromCache(t *testing.T) {
1121+
client, mux, _, teardown := setup()
1122+
defer teardown()
1123+
1124+
reset := time.Now().UTC().Add(time.Minute).Round(time.Second) // Rate reset is a minute from now, with 1 second precision.
1125+
1126+
// By adding the X-From-Cache header we pretend this is served from a cache.
1127+
mux.HandleFunc("/first", func(w http.ResponseWriter, r *http.Request) {
1128+
w.Header().Set("X-From-Cache", "1")
1129+
w.Header().Set(headerRateLimit, "60")
1130+
w.Header().Set(headerRateRemaining, "0")
1131+
w.Header().Set(headerRateReset, fmt.Sprint(reset.Unix()))
1132+
w.Header().Set("Content-Type", "application/json; charset=utf-8")
1133+
w.WriteHeader(http.StatusForbidden)
1134+
fmt.Fprintln(w, `{
1135+
"message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
1136+
"documentation_url": "https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#abuse-rate-limits"
1137+
}`)
1138+
})
1139+
1140+
madeNetworkCall := false
1141+
mux.HandleFunc("/second", func(w http.ResponseWriter, r *http.Request) {
1142+
madeNetworkCall = true
1143+
})
1144+
1145+
// First request is made so afterwards we can check the returned rate limit headers were ignored.
1146+
req, _ := client.NewRequest("GET", "first", nil)
1147+
ctx := context.Background()
1148+
client.Do(ctx, req, nil)
1149+
1150+
// Second request should not by hindered by rate limits.
1151+
req, _ = client.NewRequest("GET", "second", nil)
1152+
_, err := client.Do(ctx, req, nil)
1153+
1154+
if err != nil {
1155+
t.Fatalf("Second request failed, even though the rate limits from the cache should've been ignored: %v", err)
1156+
}
1157+
if !madeNetworkCall {
1158+
t.Fatal("Network call was not made, even though the rate limits from the cache should've been ignored")
1159+
}
1160+
}
1161+
11191162
// Ensure *AbuseRateLimitError is returned when the response indicates that
11201163
// the client has triggered an abuse detection mechanism.
11211164
func TestDo_rateLimit_abuseRateLimitError(t *testing.T) {

0 commit comments

Comments
 (0)