utils: make retry-after numeric clamp overflow-safe
This commit is contained in:
parent
9440bebca6
commit
345d4fddc9
2 changed files with 60 additions and 10 deletions
|
|
@ -60,8 +60,8 @@ func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration {
|
||||||
return clampRetryDelay(fallback)
|
return clampRetryDelay(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
if seconds, err := strconv.Atoi(retryAfter); err == nil && seconds >= 0 {
|
if delay, ok := numericRetryAfterDelay(retryAfter); ok {
|
||||||
return clampRetryDelay(time.Duration(seconds) * time.Second)
|
return delay
|
||||||
}
|
}
|
||||||
|
|
||||||
if when, err := http.ParseTime(retryAfter); err == nil {
|
if when, err := http.ParseTime(retryAfter); err == nil {
|
||||||
|
|
@ -78,6 +78,18 @@ func retryDelayForAttempt(resp *http.Response, attempt int) time.Duration {
|
||||||
return clampRetryDelay(fallback)
|
return clampRetryDelay(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func numericRetryAfterDelay(retryAfter string) (time.Duration, bool) {
|
||||||
|
seconds, err := strconv.ParseInt(retryAfter, 10, 64)
|
||||||
|
if err != nil || seconds < 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
maxSeconds := int64(maxRetrySleepDuration / time.Second)
|
||||||
|
if seconds > maxSeconds {
|
||||||
|
return maxRetrySleepDuration, true
|
||||||
|
}
|
||||||
|
return clampRetryDelay(time.Duration(seconds) * time.Second), true
|
||||||
|
}
|
||||||
|
|
||||||
func clampRetryDelay(delay time.Duration) time.Duration {
|
func clampRetryDelay(delay time.Duration) time.Duration {
|
||||||
if delay <= 0 {
|
if delay <= 0 {
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
|
|
@ -297,19 +297,43 @@ func TestRetryDelayForAttempt_DateRetryAfterUsesResponseDateHeader(t *testing.T)
|
||||||
assert.Equal(t, 10*time.Second, retryDelayForAttempt(resp, 0))
|
assert.Equal(t, 10*time.Second, retryDelayForAttempt(resp, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryDelayForAttempt_DateRetryAfterInvalidDateFallsBackSafely(t *testing.T) {
|
func TestRetryDelayForAttempt_DateRetryAfterInvalidOrMissingDateFallsBackSafely(t *testing.T) {
|
||||||
maxRetrySleepDuration = time.Minute
|
maxRetrySleepDuration = 30 * time.Second
|
||||||
t.Cleanup(func() { maxRetrySleepDuration = time.Minute })
|
t.Cleanup(func() { maxRetrySleepDuration = time.Minute })
|
||||||
|
|
||||||
resp := &http.Response{
|
retryAfterAt := time.Now().UTC().Add(3 * time.Second).Format(http.TimeFormat)
|
||||||
StatusCode: http.StatusTooManyRequests,
|
testcases := []struct {
|
||||||
Header: http.Header{
|
name string
|
||||||
"Retry-After": []string{time.Date(2000, 1, 2, 15, 4, 5, 0, time.UTC).Format(http.TimeFormat)},
|
header http.Header
|
||||||
"Date": []string{"invalid-date"},
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid-date-header",
|
||||||
|
header: http.Header{
|
||||||
|
"Retry-After": []string{retryAfterAt},
|
||||||
|
"Date": []string{"invalid-date"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing-date-header",
|
||||||
|
header: http.Header{
|
||||||
|
"Retry-After": []string{retryAfterAt},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, time.Duration(0), retryDelayForAttempt(resp, 0))
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
resp := &http.Response{
|
||||||
|
StatusCode: http.StatusTooManyRequests,
|
||||||
|
Header: tc.header,
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := retryDelayForAttempt(resp, 0)
|
||||||
|
assert.Greater(t, delay, time.Duration(0))
|
||||||
|
assert.GreaterOrEqual(t, delay, 1500*time.Millisecond)
|
||||||
|
assert.LessOrEqual(t, delay, 5*time.Second)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) {
|
func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) {
|
||||||
|
|
@ -325,3 +349,17 @@ func TestRetryDelayForAttempt_RetryAfterIsCapped(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0))
|
assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRetryDelayForAttempt_RetryAfterNumericOverflowStillCaps(t *testing.T) {
|
||||||
|
maxRetrySleepDuration = 2 * time.Second
|
||||||
|
t.Cleanup(func() { maxRetrySleepDuration = time.Minute })
|
||||||
|
|
||||||
|
resp := &http.Response{
|
||||||
|
StatusCode: http.StatusTooManyRequests,
|
||||||
|
Header: http.Header{
|
||||||
|
"Retry-After": []string{"9223372036854775807"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 2*time.Second, retryDelayForAttempt(resp, 0))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue