1package 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 17import ( 18 "bytes" 19 "crypto/tls" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "log" 24 "net/http" 25 "strings" 26 "time" 27 28 "github.com/Azure/go-autorest/logger" 29) 30 31const ( 32 // DefaultPollingDelay is a reasonable delay between polling requests. 33 DefaultPollingDelay = 60 * 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 45var ( 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 57const ( 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. 70type 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. 76func (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. 86func (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. 92type 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. 101func (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 = ioutil.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 = ioutil.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. 126func (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 = ioutil.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 = ioutil.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. 155type 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 default number of retry attempts for client. 169 RetryAttempts int 170 171 // RetryDuration sets the delay duration for retries. 172 RetryDuration time.Duration 173 174 // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent 175 // through the Do method. 176 UserAgent string 177 178 Jar http.CookieJar 179 180 // Set to true to skip attempted registration of resource providers (false by default). 181 SkipResourceProviderRegistration bool 182 183 // SendDecorators can be used to override the default chain of SendDecorators. 184 // This can be used to specify things like a custom retry SendDecorator. 185 // Set this to an empty slice to use no SendDecorators. 186 SendDecorators []SendDecorator 187} 188 189// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed 190// string. 191func NewClientWithUserAgent(ua string) Client { 192 return newClient(ua, tls.RenegotiateNever) 193} 194 195// ClientOptions contains various Client configuration options. 196type ClientOptions struct { 197 // UserAgent is an optional user-agent string to append to the default user agent. 198 UserAgent string 199 200 // Renegotiation is an optional setting to control client-side TLS renegotiation. 201 Renegotiation tls.RenegotiationSupport 202} 203 204// NewClientWithOptions returns an instance of a Client with the specified values. 205func NewClientWithOptions(options ClientOptions) Client { 206 return newClient(options.UserAgent, options.Renegotiation) 207} 208 209func newClient(ua string, renegotiation tls.RenegotiationSupport) Client { 210 c := Client{ 211 PollingDelay: DefaultPollingDelay, 212 PollingDuration: DefaultPollingDuration, 213 RetryAttempts: DefaultRetryAttempts, 214 RetryDuration: DefaultRetryDuration, 215 UserAgent: UserAgent(), 216 } 217 c.Sender = c.sender(renegotiation) 218 c.AddToUserAgent(ua) 219 return c 220} 221 222// AddToUserAgent adds an extension to the current user agent 223func (c *Client) AddToUserAgent(extension string) error { 224 if extension != "" { 225 c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension) 226 return nil 227 } 228 return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent) 229} 230 231// Do implements the Sender interface by invoking the active Sender after applying authorization. 232// If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent 233// is set, apply set the User-Agent header. 234func (c Client) Do(r *http.Request) (*http.Response, error) { 235 if r.UserAgent() == "" { 236 r, _ = Prepare(r, 237 WithUserAgent(c.UserAgent)) 238 } 239 // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations 240 r, err := Prepare(r, 241 c.WithAuthorization(), 242 c.WithInspection()) 243 if err != nil { 244 var resp *http.Response 245 if detErr, ok := err.(DetailedError); ok { 246 // if the authorization failed (e.g. invalid credentials) there will 247 // be a response associated with the error, be sure to return it. 248 resp = detErr.Response 249 } 250 return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") 251 } 252 logger.Instance.WriteRequest(r, logger.Filter{ 253 Header: func(k string, v []string) (bool, []string) { 254 // remove the auth token from the log 255 if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") { 256 v = []string{"**REDACTED**"} 257 } 258 return true, v 259 }, 260 }) 261 resp, err := SendWithSender(c.sender(tls.RenegotiateNever), r) 262 logger.Instance.WriteResponse(resp, logger.Filter{}) 263 Respond(resp, c.ByInspecting()) 264 return resp, err 265} 266 267// sender returns the Sender to which to send requests. 268func (c Client) sender(renengotiation tls.RenegotiationSupport) Sender { 269 if c.Sender == nil { 270 return sender(renengotiation) 271 } 272 return c.Sender 273} 274 275// WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator 276// from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer. 277func (c Client) WithAuthorization() PrepareDecorator { 278 return c.authorizer().WithAuthorization() 279} 280 281// authorizer returns the Authorizer to use. 282func (c Client) authorizer() Authorizer { 283 if c.Authorizer == nil { 284 return NullAuthorizer{} 285 } 286 return c.Authorizer 287} 288 289// WithInspection is a convenience method that passes the request to the supplied RequestInspector, 290// if present, or returns the WithNothing PrepareDecorator otherwise. 291func (c Client) WithInspection() PrepareDecorator { 292 if c.RequestInspector == nil { 293 return WithNothing() 294 } 295 return c.RequestInspector 296} 297 298// ByInspecting is a convenience method that passes the response to the supplied ResponseInspector, 299// if present, or returns the ByIgnoring RespondDecorator otherwise. 300func (c Client) ByInspecting() RespondDecorator { 301 if c.ResponseInspector == nil { 302 return ByIgnoring() 303 } 304 return c.ResponseInspector 305} 306 307// Send sends the provided http.Request using the client's Sender or the default sender. 308// It returns the http.Response and possible error. It also accepts a, possibly empty, 309// default set of SendDecorators used when sending the request. 310// SendDecorators have the following precedence: 311// 1. In a request's context via WithSendDecorators() 312// 2. Specified on the client in SendDecorators 313// 3. The default values specified in this method 314func (c Client) Send(req *http.Request, decorators ...SendDecorator) (*http.Response, error) { 315 if c.SendDecorators != nil { 316 decorators = c.SendDecorators 317 } 318 inCtx := req.Context().Value(ctxSendDecorators{}) 319 if sd, ok := inCtx.([]SendDecorator); ok { 320 decorators = sd 321 } 322 return SendWithSender(c, req, decorators...) 323} 324