-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest.go
More file actions
110 lines (104 loc) · 2.58 KB
/
request.go
File metadata and controls
110 lines (104 loc) · 2.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package d1http
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"net/url"
"strings"
"time"
)
func (c *Client) do(ctx context.Context, method, path string, query url.Values, body any, out any) error {
if err := c.cfg.validateBase(); err != nil {
return err
}
var bodyBytes []byte
var err error
if body != nil {
bodyBytes, err = json.Marshal(body)
if err != nil {
return err
}
}
attempts := c.cfg.Retry.MaxRetries + 1
var lastErr error
for attempt := 0; attempt < attempts; attempt++ {
if attempt > 0 {
wait := c.backoff(attempt)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(wait):
}
}
lastErr = c.doOnce(ctx, method, path, query, bodyBytes, out)
if lastErr == nil || !IsRetryable(lastErr) || attempt == attempts-1 {
return lastErr
}
}
return lastErr
}
func (c *Client) doOnce(ctx context.Context, method, path string, query url.Values, bodyBytes []byte, out any) error {
u := c.cfg.BaseURL + path
if len(query) > 0 {
u += "?" + query.Encode()
}
var body io.Reader
if bodyBytes != nil {
body = bytes.NewReader(bodyBytes)
}
req, err := http.NewRequestWithContext(ctx, method, u, body)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+c.cfg.APIToken)
req.Header.Set("User-Agent", c.cfg.UserAgent)
if bodyBytes != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.cfg.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return decodeAPIError(resp.StatusCode, respBody)
}
if out == nil {
return nil
}
if err := json.Unmarshal(respBody, out); err != nil {
return fmt.Errorf("decode cloudflare response: %w", err)
}
return nil
}
func decodeAPIError(status int, body []byte) error {
var env envelope[json.RawMessage]
if err := json.Unmarshal(body, &env); err == nil && (len(env.Errors) > 0 || len(env.Messages) > 0) {
return &APIError{StatusCode: status, Errors: env.Errors, Messages: env.Messages, Body: string(body)}
}
return &APIError{StatusCode: status, Body: string(body)}
}
func (c *Client) backoff(attempt int) time.Duration {
min := c.cfg.Retry.MinBackoff
max := c.cfg.Retry.MaxBackoff
d := time.Duration(float64(min) * math.Pow(2, float64(attempt-1)))
if d > max {
return max
}
return d
}
func apiPath(parts ...string) string {
clean := make([]string, 0, len(parts))
for _, p := range parts {
clean = append(clean, strings.Trim(p, "/"))
}
return "/" + strings.Join(clean, "/")
}