1 package autorest
2 3 // Copyright 2017 Microsoft Corporation
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 17 import (
18 "bytes"
19 "crypto/tls"
20 "errors"
21 "fmt"
22 "io"
23 "log"
24 "net/http"
25 "strings"
26 "time"
27 28 "github.com/Azure/go-autorest/logger"
29 )
30 31 const (
32 // DefaultPollingDelay is a reasonable delay between polling requests.
33 DefaultPollingDelay = 30 * time.Second
34 35 // DefaultPollingDuration is a reasonable total polling duration.
36 DefaultPollingDuration = 15 * time.Minute
37 38 // DefaultRetryAttempts is number of attempts for retry status codes (5xx).
39 DefaultRetryAttempts = 3
40 41 // DefaultRetryDuration is the duration to wait between retries.
42 DefaultRetryDuration = 30 * time.Second
43 )
44 45 var (
46 // StatusCodesForRetry are a defined group of status code for which the client will retry
47 StatusCodesForRetry = []int{
48 http.StatusRequestTimeout, // 408
49 http.StatusTooManyRequests, // 429
50 http.StatusInternalServerError, // 500
51 http.StatusBadGateway, // 502
52 http.StatusServiceUnavailable, // 503
53 http.StatusGatewayTimeout, // 504
54 }
55 )
56 57 const (
58 requestFormat = `HTTP Request Begin ===================================================
59 %s
60 ===================================================== HTTP Request End
61 `
62 responseFormat = `HTTP Response Begin ===================================================
63 %s
64 ===================================================== HTTP Response End
65 `
66 )
67 68 // Response serves as the base for all responses from generated clients. It provides access to the
69 // last http.Response.
70 type Response struct {
71 *http.Response `json:"-"`
72 }
73 74 // IsHTTPStatus returns true if the returned HTTP status code matches the provided status code.
75 // If there was no response (i.e. the underlying http.Response is nil) the return value is false.
76 func (r Response) IsHTTPStatus(statusCode int) bool {
77 if r.Response == nil {
78 return false
79 }
80 return r.Response.StatusCode == statusCode
81 }
82 83 // HasHTTPStatus returns true if the returned HTTP status code matches one of the provided status codes.
84 // If there was no response (i.e. the underlying http.Response is nil) or not status codes are provided
85 // the return value is false.
86 func (r Response) HasHTTPStatus(statusCodes ...int) bool {
87 return ResponseHasStatusCode(r.Response, statusCodes...)
88 }
89 90 // LoggingInspector implements request and response inspectors that log the full request and
91 // response to a supplied log.
92 type LoggingInspector struct {
93 Logger *log.Logger
94 }
95 96 // WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The
97 // body is restored after being emitted.
98 //
99 // Note: Since it reads the entire Body, this decorator should not be used where body streaming is
100 // important. It is best used to trace JSON or similar body values.
101 func (li LoggingInspector) WithInspection() PrepareDecorator {
102 return func(p Preparer) Preparer {
103 return PreparerFunc(func(r *http.Request) (*http.Request, error) {
104 var body, b bytes.Buffer
105 106 defer r.Body.Close()
107 108 r.Body = io.NopCloser(io.TeeReader(r.Body, &body))
109 if err := r.Write(&b); err != nil {
110 return nil, fmt.Errorf("Failed to write response: %v", err)
111 }
112 113 li.Logger.Printf(requestFormat, b.String())
114 115 r.Body = io.NopCloser(&body)
116 return p.Prepare(r)
117 })
118 }
119 }
120 121 // ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
122 // body is restored after being emitted.
123 //
124 // Note: Since it reads the entire Body, this decorator should not be used where body streaming is
125 // important. It is best used to trace JSON or similar body values.
126 func (li LoggingInspector) ByInspecting() RespondDecorator {
127 return func(r Responder) Responder {
128 return ResponderFunc(func(resp *http.Response) error {
129 var body, b bytes.Buffer
130 defer resp.Body.Close()
131 resp.Body = io.NopCloser(io.TeeReader(resp.Body, &body))
132 if err := resp.Write(&b); err != nil {
133 return fmt.Errorf("Failed to write response: %v", err)
134 }
135 136 li.Logger.Printf(responseFormat, b.String())
137 138 resp.Body = io.NopCloser(&body)
139 return r.Respond(resp)
140 })
141 }
142 }
143 144 // Client is the base for autorest generated clients. It provides default, "do nothing"
145 // implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
146 // standard, undecorated http.Client as a default Sender.
147 //
148 // Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
149 // return responses that compose with Response.
150 //
151 // Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
152 // RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
153 // breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
154 // sending the request by providing a decorated Sender.
155 type Client struct {
156 Authorizer Authorizer
157 Sender Sender
158 RequestInspector PrepareDecorator
159 ResponseInspector RespondDecorator
160 161 // PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header
162 PollingDelay time.Duration
163 164 // PollingDuration sets the maximum polling time after which an error is returned.
165 // Setting this to zero will use the provided context to control the duration.
166 PollingDuration time.Duration
167 168 // RetryAttempts sets the total number of times the client will attempt to make an HTTP request.
169 // Set the value to 1 to disable retries. DO NOT set the value to less than 1.
170 RetryAttempts int
171 172 // RetryDuration sets the delay duration for retries.
173 RetryDuration time.Duration
174 175 // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
176 // through the Do method.
177 UserAgent string
178 179 Jar http.CookieJar
180 181 // Set to true to skip attempted registration of resource providers (false by default).
182 SkipResourceProviderRegistration bool
183 184 // SendDecorators can be used to override the default chain of SendDecorators.
185 // This can be used to specify things like a custom retry SendDecorator.
186 // Set this to an empty slice to use no SendDecorators.
187 SendDecorators []SendDecorator
188 }
189 190 // NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
191 // string.
192 func NewClientWithUserAgent(ua string) Client {
193 return newClient(ua, tls.RenegotiateNever)
194 }
195 196 // ClientOptions contains various Client configuration options.
197 type ClientOptions struct {
198 // UserAgent is an optional user-agent string to append to the default user agent.
199 UserAgent string
200 201 // Renegotiation is an optional setting to control client-side TLS renegotiation.
202 Renegotiation tls.RenegotiationSupport
203 }
204 205 // NewClientWithOptions returns an instance of a Client with the specified values.
206 func NewClientWithOptions(options ClientOptions) Client {
207 return newClient(options.UserAgent, options.Renegotiation)
208 }
209 210 func newClient(ua string, renegotiation tls.RenegotiationSupport) Client {
211 c := Client{
212 PollingDelay: DefaultPollingDelay,
213 PollingDuration: DefaultPollingDuration,
214 RetryAttempts: DefaultRetryAttempts,
215 RetryDuration: DefaultRetryDuration,
216 UserAgent: UserAgent(),
217 }
218 c.Sender = c.sender(renegotiation)
219 c.AddToUserAgent(ua)
220 return c
221 }
222 223 // AddToUserAgent adds an extension to the current user agent
224 func (c *Client) AddToUserAgent(extension string) error {
225 if extension != "" {
226 c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension)
227 return nil
228 }
229 return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent)
230 }
231 232 // Do implements the Sender interface by invoking the active Sender after applying authorization.
233 // If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent
234 // is set, apply set the User-Agent header.
235 func (c Client) Do(r *http.Request) (*http.Response, error) {
236 if r.UserAgent() == "" {
237 r, _ = Prepare(r,
238 WithUserAgent(c.UserAgent))
239 }
240 // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations
241 r, err := Prepare(r,
242 c.WithAuthorization(),
243 c.WithInspection())
244 if err != nil {
245 var resp *http.Response
246 if detErr, ok := err.(DetailedError); ok {
247 // if the authorization failed (e.g. invalid credentials) there will
248 // be a response associated with the error, be sure to return it.
249 resp = detErr.Response
250 }
251 return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
252 }
253 logger.Instance.WriteRequest(r, logger.Filter{
254 Header: func(k string, v []string) (bool, []string) {
255 // remove the auth token from the log
256 if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") {
257 v = []string{"**REDACTED**"}
258 }
259 return true, v
260 },
261 })
262 resp, err := SendWithSender(c.sender(tls.RenegotiateNever), r)
263 if resp == nil && err == nil {
264 err = errors.New("autorest: received nil response and error")
265 }
266 logger.Instance.WriteResponse(resp, logger.Filter{})
267 Respond(resp, c.ByInspecting())
268 return resp, err
269 }
270 271 // sender returns the Sender to which to send requests.
272 func (c Client) sender(renengotiation tls.RenegotiationSupport) Sender {
273 if c.Sender == nil {
274 return sender(renengotiation)
275 }
276 return c.Sender
277 }
278 279 // WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
280 // from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
281 func (c Client) WithAuthorization() PrepareDecorator {
282 return c.authorizer().WithAuthorization()
283 }
284 285 // authorizer returns the Authorizer to use.
286 func (c Client) authorizer() Authorizer {
287 if c.Authorizer == nil {
288 return NullAuthorizer{}
289 }
290 return c.Authorizer
291 }
292 293 // WithInspection is a convenience method that passes the request to the supplied RequestInspector,
294 // if present, or returns the WithNothing PrepareDecorator otherwise.
295 func (c Client) WithInspection() PrepareDecorator {
296 if c.RequestInspector == nil {
297 return WithNothing()
298 }
299 return c.RequestInspector
300 }
301 302 // ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
303 // if present, or returns the ByIgnoring RespondDecorator otherwise.
304 func (c Client) ByInspecting() RespondDecorator {
305 if c.ResponseInspector == nil {
306 return ByIgnoring()
307 }
308 return c.ResponseInspector
309 }
310 311 // Send sends the provided http.Request using the client's Sender or the default sender.
312 // It returns the http.Response and possible error. It also accepts a, possibly empty,
313 // default set of SendDecorators used when sending the request.
314 // SendDecorators have the following precedence:
315 // 1. In a request's context via WithSendDecorators()
316 // 2. Specified on the client in SendDecorators
317 // 3. The default values specified in this method
318 func (c Client) Send(req *http.Request, decorators ...SendDecorator) (*http.Response, error) {
319 if c.SendDecorators != nil {
320 decorators = c.SendDecorators
321 }
322 inCtx := req.Context().Value(ctxSendDecorators{})
323 if sd, ok := inCtx.([]SendDecorator); ok {
324 decorators = sd
325 }
326 return SendWithSender(c, req, decorators...)
327 }
328