1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package jsonclient
18
19 import (
20 "bytes"
21 "context"
22 "crypto"
23 "encoding/json"
24 "errors"
25 "fmt"
26 "io"
27 "log"
28 "math/rand"
29 "net/http"
30 "net/url"
31 "strconv"
32 "strings"
33 "time"
34
35 ct "github.com/google/certificate-transparency-go"
36 "github.com/google/certificate-transparency-go/x509"
37 "golang.org/x/net/context/ctxhttp"
38 "k8s.io/klog/v2"
39 )
40
41 const maxJitter = 250 * time.Millisecond
42
43 type backoffer interface {
44
45
46
47
48 set(*time.Duration) time.Duration
49
50 decreaseMultiplier()
51
52
53 until() time.Time
54 }
55
56
57
58 type JSONClient struct {
59 uri string
60 httpClient *http.Client
61 Verifier *ct.SignatureVerifier
62 logger Logger
63 backoff backoffer
64 userAgent string
65 }
66
67
68 type Logger interface {
69
70 Printf(string, ...interface{})
71 }
72
73
74 type Options struct {
75
76
77 Logger Logger
78
79 PublicKey string
80
81 PublicKeyDER []byte
82
83 UserAgent string
84 }
85
86
87
88
89 func (opts *Options) ParsePublicKey() (crypto.PublicKey, error) {
90 if len(opts.PublicKeyDER) > 0 {
91 return x509.ParsePKIXPublicKey(opts.PublicKeyDER)
92 }
93
94 if opts.PublicKey != "" {
95 pubkey, _ , rest, err := ct.PublicKeyFromPEM([]byte(opts.PublicKey))
96 if err != nil {
97 return nil, err
98 }
99 if len(rest) > 0 {
100 return nil, errors.New("extra data found after PEM key decoded")
101 }
102 return pubkey, nil
103 }
104
105 return nil, nil
106 }
107
108 type basicLogger struct{}
109
110 func (bl *basicLogger) Printf(msg string, args ...interface{}) {
111 log.Printf(msg, args...)
112 }
113
114
115
116 type RspError struct {
117 Err error
118 StatusCode int
119 Body []byte
120 }
121
122
123 func (e RspError) Error() string {
124 return e.Err.Error()
125 }
126
127
128
129
130 func New(uri string, hc *http.Client, opts Options) (*JSONClient, error) {
131 pubkey, err := opts.ParsePublicKey()
132 if err != nil {
133 return nil, fmt.Errorf("invalid public key: %v", err)
134 }
135
136 var verifier *ct.SignatureVerifier
137 if pubkey != nil {
138 var err error
139 verifier, err = ct.NewSignatureVerifier(pubkey)
140 if err != nil {
141 return nil, err
142 }
143 }
144
145 if hc == nil {
146 hc = new(http.Client)
147 }
148 logger := opts.Logger
149 if logger == nil {
150 logger = &basicLogger{}
151 }
152 return &JSONClient{
153 uri: strings.TrimRight(uri, "/"),
154 httpClient: hc,
155 Verifier: verifier,
156 logger: logger,
157 backoff: &backoff{},
158 userAgent: opts.UserAgent,
159 }, nil
160 }
161
162
163 func (c *JSONClient) BaseURI() string {
164 return c.uri
165 }
166
167
168
169
170
171 func (c *JSONClient) GetAndParse(ctx context.Context, path string, params map[string]string, rsp interface{}) (*http.Response, []byte, error) {
172 if ctx == nil {
173 return nil, nil, errors.New("context.Context required")
174 }
175
176 vals := url.Values{}
177 for k, v := range params {
178 vals.Add(k, v)
179 }
180 fullURI := fmt.Sprintf("%s%s?%s", c.uri, path, vals.Encode())
181 klog.V(2).Infof("GET %s", fullURI)
182 httpReq, err := http.NewRequest(http.MethodGet, fullURI, nil)
183 if err != nil {
184 return nil, nil, err
185 }
186 if len(c.userAgent) != 0 {
187 httpReq.Header.Set("User-Agent", c.userAgent)
188 }
189
190 httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
191 if err != nil {
192 return nil, nil, err
193 }
194
195
196 body, err := io.ReadAll(httpRsp.Body)
197 httpRsp.Body.Close()
198 if err != nil {
199 return nil, nil, RspError{Err: fmt.Errorf("failed to read response body: %v", err), StatusCode: httpRsp.StatusCode, Body: body}
200 }
201
202 if httpRsp.StatusCode != http.StatusOK {
203 return nil, nil, RspError{Err: fmt.Errorf("got HTTP Status %q", httpRsp.Status), StatusCode: httpRsp.StatusCode, Body: body}
204 }
205
206 if err := json.NewDecoder(bytes.NewReader(body)).Decode(rsp); err != nil {
207 return nil, nil, RspError{Err: err, StatusCode: httpRsp.StatusCode, Body: body}
208 }
209
210 return httpRsp, body, nil
211 }
212
213
214
215
216
217 func (c *JSONClient) PostAndParse(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
218 if ctx == nil {
219 return nil, nil, errors.New("context.Context required")
220 }
221
222 postBody, err := json.Marshal(req)
223 if err != nil {
224 return nil, nil, err
225 }
226 fullURI := fmt.Sprintf("%s%s", c.uri, path)
227 klog.V(2).Infof("POST %s", fullURI)
228 httpReq, err := http.NewRequest(http.MethodPost, fullURI, bytes.NewReader(postBody))
229 if err != nil {
230 return nil, nil, err
231 }
232 if len(c.userAgent) != 0 {
233 httpReq.Header.Set("User-Agent", c.userAgent)
234 }
235 httpReq.Header.Set("Content-Type", "application/json")
236
237 httpRsp, err := ctxhttp.Do(ctx, c.httpClient, httpReq)
238
239
240 var body []byte
241 if httpRsp != nil {
242 body, err = io.ReadAll(httpRsp.Body)
243 httpRsp.Body.Close()
244 }
245 if err != nil {
246 if httpRsp != nil {
247 return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err}
248 }
249 return nil, nil, err
250 }
251
252 if httpRsp.StatusCode == http.StatusOK {
253 if err = json.Unmarshal(body, &rsp); err != nil {
254 return nil, nil, RspError{StatusCode: httpRsp.StatusCode, Body: body, Err: err}
255 }
256 }
257 return httpRsp, body, nil
258 }
259
260
261
262 func (c *JSONClient) waitForBackoff(ctx context.Context) error {
263 dur := time.Until(c.backoff.until().Add(time.Millisecond * time.Duration(rand.Intn(int(maxJitter.Seconds()*1000)))))
264 if dur < 0 {
265 dur = 0
266 }
267 backoffTimer := time.NewTimer(dur)
268 select {
269 case <-ctx.Done():
270 return ctx.Err()
271 case <-backoffTimer.C:
272 }
273 return nil
274 }
275
276
277
278
279 func (c *JSONClient) PostAndParseWithRetry(ctx context.Context, path string, req, rsp interface{}) (*http.Response, []byte, error) {
280 if ctx == nil {
281 return nil, nil, errors.New("context.Context required")
282 }
283 for {
284 httpRsp, body, err := c.PostAndParse(ctx, path, req, rsp)
285 if err != nil {
286
287 if err == context.Canceled || err == context.DeadlineExceeded {
288 return nil, nil, err
289 }
290 wait := c.backoff.set(nil)
291 c.logger.Printf("Request to %s failed, backing-off %s: %s", c.uri, wait, err)
292 } else {
293 switch {
294 case httpRsp.StatusCode == http.StatusOK:
295 return httpRsp, body, nil
296 case httpRsp.StatusCode == http.StatusRequestTimeout:
297
298 c.logger.Printf("Request to %s timed out, retrying immediately", c.uri)
299 case httpRsp.StatusCode == http.StatusServiceUnavailable:
300 fallthrough
301 case httpRsp.StatusCode == http.StatusTooManyRequests:
302 var backoff *time.Duration
303
304
305 if retryAfter := httpRsp.Header.Get("Retry-After"); retryAfter != "" {
306 if seconds, err := strconv.Atoi(retryAfter); err == nil {
307 b := time.Duration(seconds) * time.Second
308 backoff = &b
309 } else if date, err := time.Parse(time.RFC1123, retryAfter); err == nil {
310 b := time.Until(date)
311 backoff = &b
312 }
313 }
314 wait := c.backoff.set(backoff)
315 c.logger.Printf("Request to %s failed, backing-off for %s: got HTTP status %s", c.uri, wait, httpRsp.Status)
316 default:
317 return nil, nil, RspError{
318 StatusCode: httpRsp.StatusCode,
319 Body: body,
320 Err: fmt.Errorf("got HTTP status %q", httpRsp.Status)}
321 }
322 }
323 if err := c.waitForBackoff(ctx); err != nil {
324 return nil, nil, err
325 }
326 }
327 }
328
View as plain text