Skip to content

Commit 88de4fc

Browse files
committed
Add IsRetryableStatusCode function to handle retryable HTTP status codes
1 parent a796274 commit 88de4fc

File tree

2 files changed

+98
-3
lines changed

2 files changed

+98
-3
lines changed

errors/http_error_handling.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,17 @@ func IsTransientError(resp *http.Response) bool {
130130
}
131131
return resp != nil && transientStatusCodes[resp.StatusCode]
132132
}
133+
134+
// IsRetryableStatusCode checks if the provided HTTP status code is considered retryable.
135+
func IsRetryableStatusCode(statusCode int) bool {
136+
retryableStatusCodes := map[int]bool{
137+
http.StatusTooManyRequests: true, // 429
138+
http.StatusInternalServerError: true, // 500
139+
http.StatusBadGateway: true, // 502
140+
http.StatusServiceUnavailable: true, // 503
141+
http.StatusGatewayTimeout: true, // 504
142+
}
143+
144+
_, retryable := retryableStatusCodes[statusCode]
145+
return retryable
146+
}

httpclient/httpclient_request.go

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,91 @@ func (c *Client) DoRequestV2(method, endpoint string, body, out interface{}, log
2929
// GET / PUT / DELETE
3030
func (c *Client) executeRequestWithRetries(method, endpoint string, body, out interface{}, log logger.Logger) (*http.Response, error) {
3131
// Include the core logic for handling non-idempotent requests with retries here.
32-
log.Debug("Executing non-idempotent request with retries", zap.String("method", method), zap.String("endpoint", endpoint))
32+
log.Debug("Executing request with retries", zap.String("method", method), zap.String("endpoint", endpoint))
3333

34-
// Placeholder for actual implementation
35-
return nil, log.Error("executeRequestWithRetries function is not implemented yet")
34+
// Auth Token validation check
35+
valid, err := c.ValidAuthTokenCheck(log)
36+
if err != nil || !valid {
37+
return nil, err
38+
}
39+
40+
// Acquire a token for concurrency management
41+
ctx, err := c.AcquireConcurrencyToken(context.Background(), log)
42+
if err != nil {
43+
return nil, err
44+
}
45+
defer func() {
46+
// Extract the requestID from the context and release the concurrency token
47+
if requestID, ok := ctx.Value(requestIDKey{}).(uuid.UUID); ok {
48+
c.ConcurrencyMgr.Release(requestID)
49+
}
50+
}()
51+
52+
// Determine which set of encoding and content-type request rules to use
53+
apiHandler := c.APIHandler
54+
55+
// Marshal Request with correct encoding
56+
requestData, err := apiHandler.MarshalRequest(body, method, endpoint, log)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
// Construct URL using the ConstructAPIResourceEndpoint function
62+
url := c.APIHandler.ConstructAPIResourceEndpoint(c.InstanceName, endpoint, log)
63+
64+
// Initialize total request counter
65+
c.PerfMetrics.lock.Lock()
66+
c.PerfMetrics.TotalRequests++
67+
c.PerfMetrics.lock.Unlock()
68+
69+
// Perform Request
70+
req, err := http.NewRequest(method, url, bytes.NewBuffer(requestData))
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
// Set request headers
76+
headerManager := NewHeaderManager(req, log, c.APIHandler, c.Token)
77+
headerManager.SetRequestHeaders(endpoint)
78+
headerManager.LogHeaders(c)
79+
80+
// Define a retry deadline based on the client's total retry duration configuration
81+
totalRetryDeadline := time.Now().Add(c.clientConfig.ClientOptions.TotalRetryDuration)
82+
83+
var resp *http.Response
84+
retryCount := 0
85+
for time.Now().Before(totalRetryDeadline) { // Check if the current time is before the total retry deadline
86+
req = req.WithContext(ctx)
87+
resp, err = c.executeHTTPRequest(req, log, method, endpoint)
88+
if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 {
89+
return resp, c.handleSuccessResponse(resp, out, log, method, endpoint)
90+
}
91+
92+
if errors.IsRateLimitError(resp) || errors.IsTransientError(resp) {
93+
retryCount++
94+
if retryCount > c.clientConfig.ClientOptions.MaxRetryAttempts {
95+
log.Warn("Max retry attempts reached", zap.String("method", method), zap.String("endpoint", endpoint))
96+
break
97+
}
98+
waitDuration := calculateBackoff(retryCount)
99+
log.Warn("Retrying request due to error", zap.String("method", method), zap.String("endpoint", endpoint), zap.Int("retryCount", retryCount), zap.Duration("waitDuration", waitDuration), zap.Error(err))
100+
time.Sleep(waitDuration)
101+
continue
102+
}
103+
104+
if err != nil || !errors.IsRetryableStatusCode(resp.StatusCode) {
105+
if apiErr := errors.HandleAPIError(resp, log); apiErr != nil {
106+
err = apiErr
107+
}
108+
break
109+
}
110+
}
111+
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
return resp, errors.HandleAPIError(resp, log)
36117
}
37118

38119
// executeRequest handles the execution of idempotent HTTP requests without retry logic.

0 commit comments

Comments
 (0)