forked from Azure/azure-sdk-for-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
288 lines (241 loc) · 9.32 KB
/
client.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package autorest
import (
"bytes"
"io"
"io/ioutil"
"log"
"net/http"
"time"
)
const (
// DefaultPollingDelay is the default delay between polling requests (only used if the
// http.Request lacks a well-formed Retry-After header).
DefaultPollingDelay = 60 * time.Second
// DefaultPollingDuration is the default total polling duration.
DefaultPollingDuration = 15 * time.Minute
)
// PollingMode sets how, if at all, clients composed with Client will poll.
type PollingMode string
const (
// PollUntilAttempts polling mode polls until reaching a maximum number of attempts.
PollUntilAttempts PollingMode = "poll-until-attempts"
// PollUntilDuration polling mode polls until a specified time.Duration has passed.
PollUntilDuration PollingMode = "poll-until-duration"
// DoNotPoll disables polling.
DoNotPoll PollingMode = "not-at-all"
)
const (
requestFormat = `HTTP Request Begin ===================================================
%s
===================================================== HTTP Request End
`
responseFormat = `HTTP Response Begin ===================================================
%s
===================================================== HTTP Response End
`
)
// LoggingInspector implements request and response inspectors that log the full request and
// response to a supplied log.
type LoggingInspector struct {
Logger *log.Logger
}
// WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The
// body is restored after being emitted.
//
// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
// important. It is best used to trace JSON or similar body values.
func (li LoggingInspector) WithInspection() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
var body, b bytes.Buffer
defer r.Body.Close()
r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body))
r.Write(&b)
li.Logger.Printf(requestFormat, b.String())
r.Body = ioutil.NopCloser(&body)
return p.Prepare(r)
})
}
}
// ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
// body is restored after being emitted.
//
// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
// important. It is best used to trace JSON or similar body values.
func (li LoggingInspector) ByInspecting() RespondDecorator {
return func(r Responder) Responder {
return ResponderFunc(func(resp *http.Response) error {
var body, b bytes.Buffer
defer resp.Body.Close()
resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body))
resp.Write(&b)
li.Logger.Printf(responseFormat, b.String())
resp.Body = ioutil.NopCloser(&body)
return r.Respond(resp)
})
}
}
var (
// DefaultClient is the base from which generated clients should create a Client instance. Users
// can then established widely used Client defaults by replacing or modifying the DefaultClient
// before instantiating a generated client.
DefaultClient = Client{PollingMode: PollUntilDuration, PollingDuration: DefaultPollingDuration}
)
// Client is the base for autorest generated clients. It provides default, "do nothing"
// implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
// standard, undecorated http.Client as a default Sender. Lastly, it supports basic request polling,
// limited to a maximum number of attempts or a specified duration.
//
// Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
// return responses that compose with Response.
//
// Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
// RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
// breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
// sending the request by providing a decorated Sender.
type Client struct {
Authorizer Authorizer
Sender Sender
RequestInspector PrepareDecorator
ResponseInspector RespondDecorator
PollingMode PollingMode
PollingAttempts int
PollingDuration time.Duration
// UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
// through the Do method.
UserAgent string
}
// NewClientWithUserAgent returns an instance of the DefaultClient with the UserAgent set to the
// passed string.
func NewClientWithUserAgent(ua string) Client {
c := DefaultClient
c.UserAgent = ua
return c
}
// IsPollingAllowed returns an error if the client allows polling and the passed http.Response
// requires it, otherwise it returns nil.
func (c Client) IsPollingAllowed(resp *http.Response, codes ...int) error {
if c.DoNotPoll() && ResponseRequiresPolling(resp, codes...) {
return NewError("autorest/Client", "IsPollingAllowed", "Response to %s requires polling but polling is disabled",
resp.Request.URL)
}
return nil
}
// PollAsNeeded is a convenience method that will poll if the passed http.Response requires it.
func (c Client) PollAsNeeded(resp *http.Response, codes ...int) (*http.Response, error) {
if !ResponseRequiresPolling(resp, codes...) {
return resp, nil
}
if c.DoNotPoll() {
return resp, NewError("autorest/Client", "PollAsNeeded", "Polling for %s is required, but polling is disabled",
resp.Request.URL)
}
req, err := NewPollingRequest(resp, c)
if err != nil {
return resp, NewErrorWithError(err, "autorest/Client", "PollAsNeeded", "Unable to create polling request for response to %s",
resp.Request.URL)
}
Prepare(req,
c.WithInspection())
if c.PollForAttempts() {
return PollForAttempts(c, req, DefaultPollingDelay, c.PollingAttempts, codes...)
}
return PollForDuration(c, req, DefaultPollingDelay, c.PollingDuration, codes...)
}
// DoNotPoll returns true if the client should not poll, false otherwise.
func (c Client) DoNotPoll() bool {
return len(c.PollingMode) == 0 || c.PollingMode == DoNotPoll
}
// PollForAttempts returns true if the PollingMode is set to ForAttempts, false otherwise.
func (c Client) PollForAttempts() bool {
return c.PollingMode == PollUntilAttempts
}
// PollForDuration return true if the PollingMode is set to ForDuration, false otherwise.
func (c Client) PollForDuration() bool {
return c.PollingMode == PollUntilDuration
}
// Send sends the passed http.Request after applying authorization. It will poll if the client
// allows polling and the http.Response status code requires it. It will close the http.Response
// Body if the request returns an error.
func (c Client) Send(req *http.Request, codes ...int) (*http.Response, error) {
if len(codes) == 0 {
codes = []int{http.StatusOK}
}
req, err := Prepare(req,
c.WithAuthorization(),
c.WithInspection())
if err != nil {
return nil, NewErrorWithError(err, "autorest/Client", "Send", "Preparing request failed")
}
resp, err := SendWithSender(c, req,
DoErrorUnlessStatusCode(codes...))
if err == nil {
err = c.IsPollingAllowed(resp)
if err == nil {
resp, err = c.PollAsNeeded(resp)
}
}
if err != nil {
Respond(resp,
ByClosing())
}
return resp, err
}
// Do implements the Sender interface by invoking the active Sender. If Sender is not set, it uses
// a new instance of http.Client. In both cases it will, if UserAgent is set, apply set the
// User-Agent header.
func (c Client) Do(r *http.Request) (*http.Response, error) {
if len(c.UserAgent) > 0 {
r, _ = Prepare(r, WithUserAgent(c.UserAgent))
}
return c.sender().Do(r)
}
// sender returns the Sender to which to send requests.
func (c Client) sender() Sender {
if c.Sender == nil {
return &http.Client{}
}
return c.Sender
}
// WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
// from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
func (c Client) WithAuthorization() PrepareDecorator {
return c.authorizer().WithAuthorization()
}
// authorizer returns the Authorizer to use.
func (c Client) authorizer() Authorizer {
if c.Authorizer == nil {
return NullAuthorizer{}
}
return c.Authorizer
}
// WithInspection is a convenience method that passes the request to the supplied RequestInspector,
// if present, or returns the WithNothing PrepareDecorator otherwise.
func (c Client) WithInspection() PrepareDecorator {
if c.RequestInspector == nil {
return WithNothing()
}
return c.RequestInspector
}
// ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
// if present, or returns the ByIgnoring RespondDecorator otherwise.
func (c Client) ByInspecting() RespondDecorator {
if c.ResponseInspector == nil {
return ByIgnoring()
}
return c.ResponseInspector
}
// Response serves as the base for all responses from generated clients. It provides access to the
// last http.Response.
type Response struct {
*http.Response `json:"-"`
}
// GetPollingDelay extracts the polling delay from the Retry-After header of the response. If
// the header is absent or is malformed, it will return the supplied default delay time.Duration.
func (r Response) GetPollingDelay(defaultDelay time.Duration) time.Duration {
return GetPollingDelay(r.Response, defaultDelay)
}
// GetPollingLocation retrieves the polling URL from the Location header of the response.
func (r Response) GetPollingLocation() string {
return GetPollingLocation(r.Response)
}