Skip to content

Commit f8af39d

Browse files
committed
feat(agent): deal with 429 and 503 HTTP status and Retry-After header
This update introduces handling for the 429 and 503 HTTP status code, which indicates that the user has sent too many requests or the service is temporary unavailable in a time. The agent will now respect the Retry-After header, allowing for more efficient request management and reducing the likelihood of overwhelming the server. Additionally, the default retry time has been changed from 2 seconds to 5 seconds to provide a more reasonable wait time between retries, aligning with best practices for rate limiting and improving overall performance.
1 parent 92e36a4 commit f8af39d

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

pkg/api/client/client.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"net"
99
"net/http"
1010
"net/url"
11+
"strconv"
12+
"time"
1113

1214
resty "github.com/go-resty/resty/v2"
1315
"github.com/shellhub-io/shellhub/pkg/models"
@@ -55,27 +57,76 @@ func NewClient(address string, opts ...Opt) (Client, error) {
5557
return nil, errors.Join(ErrParseAddress, err)
5658
}
5759

60+
const RetryAfterHeader string = "Retry-After"
61+
62+
// DefaultMaxRetryWaitTime is the default value for wait time between retries.
63+
const DefaultMaxRetryWaitTime time.Duration = 1 * time.Hour
64+
// DefaultRetryAfterTime is the retry default time when the header [RetryAfterHeader] isn't defined on the response.
65+
const DefaultRetryAfterTime time.Duration = 5 * time.Second
66+
5867
client := new(client)
5968
client.http = resty.New()
6069
client.http.SetRetryCount(math.MaxInt32)
6170
client.http.SetRedirectPolicy(SameDomainRedirectPolicy())
6271
client.http.SetBaseURL(uri.String())
6372
client.http.AddRetryCondition(func(r *resty.Response, err error) bool {
6473
if _, ok := err.(net.Error); ok {
74+
log.WithFields(log.Fields{
75+
"url": r.Request.URL,
76+
}).WithError(err).Error("network error")
77+
6578
return true
6679
}
6780

68-
if r.StatusCode() >= http.StatusInternalServerError && r.StatusCode() != http.StatusNotImplemented {
81+
switch {
82+
case r.StatusCode() == http.StatusTooManyRequests:
6983
log.WithFields(log.Fields{
7084
"status_code": r.StatusCode(),
7185
"url": r.Request.URL,
86+
"data": r.String(),
87+
}).Warn("too many requests")
88+
89+
return true
90+
case r.StatusCode() >= http.StatusInternalServerError && r.StatusCode() != http.StatusNotImplemented:
91+
log.WithFields(log.Fields{
92+
"status_code": r.StatusCode(),
93+
"url": r.Request.URL,
94+
"data": r.String(),
7295
}).Warn("failed to achieve the server")
7396

7497
return true
7598
}
7699

77100
return false
78101
})
102+
client.http.SetRetryAfter(func(c *resty.Client, r *resty.Response) (time.Duration, error) {
103+
switch r.StatusCode() {
104+
case http.StatusTooManyRequests, http.StatusServiceUnavailable:
105+
retryAfterHeader := r.Header().Get(RetryAfterHeader)
106+
if retryAfterHeader == "" {
107+
return DefaultRetryAfterTime, nil
108+
}
109+
110+
// NOTE: The `Retry-After` supports delay in seconds and and a date time, but currently we will support only
111+
// one of them.
112+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
113+
retryAfterSeconds, err := strconv.Atoi(retryAfterHeader)
114+
if err != nil {
115+
return DefaultRetryAfterTime, err
116+
}
117+
118+
log.WithFields(log.Fields{
119+
"status": r.StatusCode(),
120+
"retry_after": retryAfterSeconds,
121+
"url": r.Request.URL,
122+
}).Debug("retrying request after a defined time period")
123+
124+
return time.Duration(retryAfterSeconds) * time.Second, nil
125+
default:
126+
return DefaultRetryAfterTime, nil
127+
}
128+
})
129+
client.http.SetRetryMaxWaitTime(DefaultMaxRetryWaitTime)
79130

80131
if client.logger != nil {
81132
client.http.SetLogger(&LeveledLogger{client.logger})

pkg/api/client/client_public.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (c *client) AuthDevice(req *models.DeviceAuthRequest) (*models.DeviceAuthRe
4545
"tenant_id": req.TenantID,
4646
"identity": identity(req.Identity.MAC, req.Hostname),
4747
"status_code": r.StatusCode(),
48+
"data": r.String(),
4849
}).Warn("failed to authenticate device")
4950

5051
return true

0 commit comments

Comments
 (0)