-
Notifications
You must be signed in to change notification settings - Fork 103
/
Copy pathrequest.go
140 lines (119 loc) · 3.75 KB
/
request.go
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tfe
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
retryablehttp "github.com/hashicorp/go-retryablehttp"
"golang.org/x/time/rate"
)
// ClientRequest encapsulates a request sent by the Client
type ClientRequest struct {
retryableRequest *retryablehttp.Request
http *retryablehttp.Client
limiter *rate.Limiter
// Header are the headers that will be sent in this request
Header http.Header
}
func (r ClientRequest) Do(ctx context.Context, model interface{}) error {
// Wait will block until the limiter can obtain a new token
// or returns an error if the given context is canceled.
if r.limiter != nil {
if err := r.limiter.Wait(ctx); err != nil {
return err
}
}
// If the caller provided a response header hook then we'll call it
// once we have a response.
respHeaderHook := contextResponseHeaderHook(ctx)
// Add the context to the request.
reqWithCxt := r.retryableRequest.WithContext(ctx)
// Execute the request and check the response.
resp, err := r.http.Do(reqWithCxt)
if resp != nil {
// We call the callback whenever there's any sort of response,
// even if it's returned in conjunction with an error.
respHeaderHook(resp.StatusCode, resp.Header)
}
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return ctx.Err()
default:
return err
}
}
defer resp.Body.Close()
// Basic response checking.
if err := checkResponseCode(resp); err != nil {
return err
}
// Return here if decoding the response isn't needed.
if model == nil {
return nil
}
// If v implements io.Writer, write the raw response body.
if w, ok := model.(io.Writer); ok {
_, err := io.Copy(w, resp.Body)
return err
}
return unmarshalResponse(resp.Body, model)
}
// DoJSON is similar to Do except that it should be used when a plain JSON response is expected
// as opposed to json-api.
func (r *ClientRequest) DoJSON(ctx context.Context, model any) error {
// Wait will block until the limiter can obtain a new token
// or returns an error if the given context is canceled.
if r.limiter != nil {
if err := r.limiter.Wait(ctx); err != nil {
return err
}
}
// Add the context to the request.
contextReq := r.retryableRequest.WithContext(ctx)
// If the caller provided a response header hook then we'll call it
// once we have a response.
respHeaderHook := contextResponseHeaderHook(ctx)
// Execute the request and check the response.
resp, err := r.http.Do(contextReq)
if resp != nil {
// We call the callback whenever there's any sort of response,
// even if it's returned in conjunction with an error.
respHeaderHook(resp.StatusCode, resp.Header)
}
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return ctx.Err()
default:
return err
}
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return fmt.Errorf("error HTTP response: %d", resp.StatusCode)
} else if resp.StatusCode == 304 {
// Got a "Not Modified" response, but we can't return a model because there is no response body.
// This is necessary to support the IPRanges endpoint, which has the peculiar behavior
// of not returning content but allowing a 304 response by optionally sending an
// If-Modified-Since header.
return nil
}
// Return here if decoding the response isn't needed.
if model == nil {
return nil
}
// If v implements io.Writer, write the raw response body.
if w, ok := model.(io.Writer); ok {
_, err := io.Copy(w, resp.Body)
return err
}
return json.NewDecoder(resp.Body).Decode(model)
}