mirror of
https://github.com/ProtonMail/go-proton-api.git
synced 2025-12-23 15:47:52 -05:00
fix(GODT-2514): Apply Retry-After to 503 status
Apply the same retry-after code for 429 replies request to 503 replies.
This commit is contained in:
committed by
LBeernaertProton
parent
71c20587e0
commit
2751384cef
@@ -102,6 +102,41 @@ func TestHandleTooManyRequests(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyRequests503(t *testing.T) {
|
||||
// Create a server with a rate limit of 1 request per second.
|
||||
s := server.New(server.WithRateLimitAndCustomStatusCode(1, time.Second, http.StatusServiceUnavailable))
|
||||
defer s.Close()
|
||||
|
||||
var calls []server.Call
|
||||
|
||||
// Watch the calls made.
|
||||
s.AddCallWatcher(func(call server.Call) {
|
||||
calls = append(calls, call)
|
||||
})
|
||||
|
||||
m := proton.New(
|
||||
proton.WithHostURL(s.GetHostURL()),
|
||||
proton.WithTransport(proton.InsecureTransport()),
|
||||
)
|
||||
defer m.Close()
|
||||
|
||||
// Make five calls; they should all succeed, but will be rate limited.
|
||||
for i := 0; i < 5; i++ {
|
||||
require.NoError(t, m.Ping(context.Background()))
|
||||
}
|
||||
|
||||
// After each 503 response, we should wait at least the requested duration before making the next request.
|
||||
for idx, call := range calls {
|
||||
if call.Status == http.StatusServiceUnavailable {
|
||||
after, err := strconv.Atoi(call.ResponseHeader.Get("Retry-After"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// The next call should be made after the requested duration.
|
||||
require.True(t, calls[idx+1].Time.After(call.Time.Add(time.Duration(after)*time.Second)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleTooManyRequests_Malformed(t *testing.T) {
|
||||
var calls []time.Time
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func updateTime(_ *resty.Client, res *resty.Response) error {
|
||||
// nolint:gosec
|
||||
func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error) {
|
||||
// 0 and no error means default behaviour which is exponential backoff with jitter.
|
||||
if res.StatusCode() != http.StatusTooManyRequests {
|
||||
if res.StatusCode() != http.StatusTooManyRequests && res.StatusCode() != http.StatusServiceUnavailable {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error
|
||||
}
|
||||
|
||||
func catchTooManyRequests(res *resty.Response, _ error) bool {
|
||||
return res.StatusCode() == http.StatusTooManyRequests
|
||||
return res.StatusCode() == http.StatusTooManyRequests || res.StatusCode() == http.StatusServiceUnavailable
|
||||
}
|
||||
|
||||
func catchDialError(res *resty.Response, err error) bool {
|
||||
|
||||
@@ -22,12 +22,16 @@ type rateLimiter struct {
|
||||
|
||||
// countLock is a mutex for the callCount.
|
||||
countLock sync.Mutex
|
||||
|
||||
// statusCode to reply with
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func newRateLimiter(limit int, window time.Duration) *rateLimiter {
|
||||
func newRateLimiter(limit int, window time.Duration, statusCode int) *rateLimiter {
|
||||
return &rateLimiter{
|
||||
limit: limit,
|
||||
window: window,
|
||||
limit: limit,
|
||||
window: window,
|
||||
statusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ func (s *Server) applyRateLimit() gin.HandlerFunc {
|
||||
|
||||
if wait := s.rateLimit.exceeded(); wait > 0 {
|
||||
c.Header("Retry-After", strconv.Itoa(int(wait.Seconds())))
|
||||
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||
c.AbortWithStatus(s.rateLimit.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,18 +186,28 @@ func (opt withAuthCache) config(builder *serverBuilder) {
|
||||
|
||||
func WithRateLimit(limit int, window time.Duration) Option {
|
||||
return &withRateLimit{
|
||||
limit: limit,
|
||||
window: window,
|
||||
limit: limit,
|
||||
window: window,
|
||||
statusCode: http.StatusTooManyRequests,
|
||||
}
|
||||
}
|
||||
|
||||
func WithRateLimitAndCustomStatusCode(limit int, window time.Duration, code int) Option {
|
||||
return &withRateLimit{
|
||||
limit: limit,
|
||||
window: window,
|
||||
statusCode: code,
|
||||
}
|
||||
}
|
||||
|
||||
type withRateLimit struct {
|
||||
limit int
|
||||
window time.Duration
|
||||
limit int
|
||||
statusCode int
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
func (opt withRateLimit) config(builder *serverBuilder) {
|
||||
builder.rateLimiter = newRateLimiter(opt.limit, opt.window)
|
||||
builder.rateLimiter = newRateLimiter(opt.limit, opt.window, opt.statusCode)
|
||||
}
|
||||
|
||||
func WithProxyTransport(transport *http.Transport) Option {
|
||||
|
||||
Reference in New Issue
Block a user