|
8 | 8 | "io" |
9 | 9 | "net/http" |
10 | 10 | "net/url" |
| 11 | + "strings" |
11 | 12 | "time" |
12 | 13 |
|
13 | 14 | "github.com/veertuinc/anka-cloud-gitlab-executor/internal/gitlab" |
@@ -39,6 +40,30 @@ func (c *APIClient) parse(body []byte) (response, error) { |
39 | 40 | return r, nil |
40 | 41 | } |
41 | 42 |
|
| 43 | +// readResponseBodyWithRetry reads the response body and retries once on unexpected EOF |
| 44 | +func (c *APIClient) readResponseBodyWithRetry(resp *http.Response, req *http.Request) ([]byte, *http.Response, error) { |
| 45 | + bodyBytes, err := io.ReadAll(resp.Body) |
| 46 | + if err != nil { |
| 47 | + if strings.Contains(err.Error(), "unexpected EOF") { |
| 48 | + time.Sleep(5 * time.Second) |
| 49 | + // Retry once on unexpected EOF |
| 50 | + retryResp, retryErr := c.HttpClient.Do(req) |
| 51 | + if retryErr != nil { |
| 52 | + return nil, nil, retryErr |
| 53 | + } |
| 54 | + defer retryResp.Body.Close() |
| 55 | + |
| 56 | + bodyBytes, retryErr = io.ReadAll(retryResp.Body) |
| 57 | + if retryErr != nil { |
| 58 | + return nil, nil, fmt.Errorf("failed to read response body (retry): %w", retryErr) |
| 59 | + } |
| 60 | + return bodyBytes, retryResp, nil |
| 61 | + } |
| 62 | + return nil, nil, fmt.Errorf("failed to read response body: %w", err) |
| 63 | + } |
| 64 | + return bodyBytes, resp, nil |
| 65 | +} |
| 66 | + |
42 | 67 | func toQueryParams(params map[string]string) url.Values { |
43 | 68 | query := url.Values{} |
44 | 69 | for k, v := range params { |
@@ -75,9 +100,12 @@ func (c *APIClient) Post(ctx context.Context, endpoint string, payload interface |
75 | 100 | } |
76 | 101 | defer r.Body.Close() |
77 | 102 |
|
78 | | - bodyBytes, err := io.ReadAll(r.Body) |
| 103 | + bodyBytes, r, err := c.readResponseBodyWithRetry(r, req) |
79 | 104 | if err != nil { |
80 | | - return nil, fmt.Errorf("failed to read response body: %w", err) |
| 105 | + if e, ok := err.(*url.Error); ok && e.Timeout() { |
| 106 | + return nil, gitlab.TransientError(fmt.Errorf("failed to send POST request to %s with payload %+v (retry): %w", endpointUrl, payload, e)) |
| 107 | + } |
| 108 | + return nil, err |
81 | 109 | } |
82 | 110 |
|
83 | 111 | baseResponse, err := c.parse(bodyBytes) |
@@ -121,9 +149,12 @@ func (c *APIClient) Delete(ctx context.Context, endpoint string, payload interfa |
121 | 149 | } |
122 | 150 | defer r.Body.Close() |
123 | 151 |
|
124 | | - bodyBytes, err := io.ReadAll(r.Body) |
| 152 | + bodyBytes, r, err := c.readResponseBodyWithRetry(r, req) |
125 | 153 | if err != nil { |
126 | | - return nil, fmt.Errorf("failed to read response body: %w", err) |
| 154 | + if e, ok := err.(*url.Error); ok && e.Timeout() { |
| 155 | + return nil, gitlab.TransientError(fmt.Errorf("failed to send DELETE request to %s with payload %+v (retry): %w", endpointUrl, payload, e)) |
| 156 | + } |
| 157 | + return nil, err |
127 | 158 | } |
128 | 159 |
|
129 | 160 | baseResponse, err := c.parse(bodyBytes) |
@@ -164,9 +195,12 @@ func (c *APIClient) Get(ctx context.Context, endpoint string, queryParams map[st |
164 | 195 | } |
165 | 196 | defer r.Body.Close() |
166 | 197 |
|
167 | | - bodyBytes, err := io.ReadAll(r.Body) |
| 198 | + bodyBytes, r, err := c.readResponseBodyWithRetry(r, req) |
168 | 199 | if err != nil { |
169 | | - return nil, fmt.Errorf("failed to read response body: %w", err) |
| 200 | + if e, ok := err.(*url.Error); ok && e.Timeout() { |
| 201 | + return nil, gitlab.TransientError(fmt.Errorf("failed to send GET request to %s (retry): %w", endpointUrl, e)) |
| 202 | + } |
| 203 | + return nil, err |
170 | 204 | } |
171 | 205 |
|
172 | 206 | baseResponse, err := c.parse(bodyBytes) |
|
0 commit comments