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 "net/http/cookiejar" 26 "strings" 27 "time" 28 29 "github.com/Azure/go-autorest/logger" 30 "github.com/Azure/go-autorest/tracing" 31) 32 33const ( 34 // DefaultPollingDelay is a reasonable delay between polling requests. 35 DefaultPollingDelay = 60 * time.Second 36 37 // DefaultPollingDuration is a reasonable total polling duration. 38 DefaultPollingDuration = 15 * time.Minute 39 40 // DefaultRetryAttempts is number of attempts for retry status codes (5xx). 41 DefaultRetryAttempts = 3 42 43 // DefaultRetryDuration is the duration to wait between retries. 44 DefaultRetryDuration = 30 * time.Second 45) 46 47var ( 48 // StatusCodesForRetry are a defined group of status code for which the client will retry 49 StatusCodesForRetry = []int{ 50 http.StatusRequestTimeout, // 408 51 http.StatusTooManyRequests, // 429 52 http.StatusInternalServerError, // 500 53 http.StatusBadGateway, // 502 54 http.StatusServiceUnavailable, // 503 55 http.StatusGatewayTimeout, // 504 56 } 57) 58 59const ( 60 requestFormat = `HTTP Request Begin =================================================== 61%s 62===================================================== HTTP Request End 63` 64 responseFormat = `HTTP Response Begin =================================================== 65%s 66===================================================== HTTP Response End 67` 68) 69 70// Response serves as the base for all responses from generated clients. It provides access to the 71// last http.Response. 72type Response struct { 73 *http.Response `json:"-"` 74} 75 76// LoggingInspector implements request and response inspectors that log the full request and 77// response to a supplied log. 78type LoggingInspector struct { 79 Logger *log.Logger 80} 81 82// WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The 83// body is restored after being emitted. 84// 85// Note: Since it reads the entire Body, this decorator should not be used where body streaming is 86// important. It is best used to trace JSON or similar body values. 87func (li LoggingInspector) WithInspection() PrepareDecorator { 88 return func(p Preparer) Preparer { 89 return PreparerFunc(func(r *http.Request) (*http.Request, error) { 90 var body, b bytes.Buffer 91 92 defer r.Body.Close() 93 94 r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body)) 95 if err := r.Write(&b); err != nil { 96 return nil, fmt.Errorf("Failed to write response: %v", err) 97 } 98 99 li.Logger.Printf(requestFormat, b.String()) 100 101 r.Body = ioutil.NopCloser(&body) 102 return p.Prepare(r) 103 }) 104 } 105} 106 107// ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The 108// body is restored after being emitted. 109// 110// Note: Since it reads the entire Body, this decorator should not be used where body streaming is 111// important. It is best used to trace JSON or similar body values. 112func (li LoggingInspector) ByInspecting() RespondDecorator { 113 return func(r Responder) Responder { 114 return ResponderFunc(func(resp *http.Response) error { 115 var body, b bytes.Buffer 116 defer resp.Body.Close() 117 resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body)) 118 if err := resp.Write(&b); err != nil { 119 return fmt.Errorf("Failed to write response: %v", err) 120 } 121 122 li.Logger.Printf(responseFormat, b.String()) 123 124 resp.Body = ioutil.NopCloser(&body) 125 return r.Respond(resp) 126 }) 127 } 128} 129 130// Client is the base for autorest generated clients. It provides default, "do nothing" 131// implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the 132// standard, undecorated http.Client as a default Sender. 133// 134// Generated clients should also use Error (see NewError and NewErrorWithError) for errors and 135// return responses that compose with Response. 136// 137// Most customization of generated clients is best achieved by supplying a custom Authorizer, custom 138// RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit 139// breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence 140// sending the request by providing a decorated Sender. 141type Client struct { 142 Authorizer Authorizer 143 Sender Sender 144 RequestInspector PrepareDecorator 145 ResponseInspector RespondDecorator 146 147 // PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header 148 PollingDelay time.Duration 149 150 // PollingDuration sets the maximum polling time after which an error is returned. 151 // Setting this to zero will use the provided context to control the duration. 152 PollingDuration time.Duration 153 154 // RetryAttempts sets the default number of retry attempts for client. 155 RetryAttempts int 156 157 // RetryDuration sets the delay duration for retries. 158 RetryDuration time.Duration 159 160 // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent 161 // through the Do method. 162 UserAgent string 163 164 Jar http.CookieJar 165 166 // Set to true to skip attempted registration of resource providers (false by default). 167 SkipResourceProviderRegistration bool 168} 169 170// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed 171// string. 172func NewClientWithUserAgent(ua string) Client { 173 c := Client{ 174 PollingDelay: DefaultPollingDelay, 175 PollingDuration: DefaultPollingDuration, 176 RetryAttempts: DefaultRetryAttempts, 177 RetryDuration: DefaultRetryDuration, 178 UserAgent: UserAgent(), 179 } 180 c.Sender = c.sender() 181 c.AddToUserAgent(ua) 182 return c 183} 184 185// AddToUserAgent adds an extension to the current user agent 186func (c *Client) AddToUserAgent(extension string) error { 187 if extension != "" { 188 c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension) 189 return nil 190 } 191 return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent) 192} 193 194// Do implements the Sender interface by invoking the active Sender after applying authorization. 195// If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent 196// is set, apply set the User-Agent header. 197func (c Client) Do(r *http.Request) (*http.Response, error) { 198 if r.UserAgent() == "" { 199 r, _ = Prepare(r, 200 WithUserAgent(c.UserAgent)) 201 } 202 // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations 203 r, err := Prepare(r, 204 c.WithAuthorization(), 205 c.WithInspection()) 206 if err != nil { 207 var resp *http.Response 208 if detErr, ok := err.(DetailedError); ok { 209 // if the authorization failed (e.g. invalid credentials) there will 210 // be a response associated with the error, be sure to return it. 211 resp = detErr.Response 212 } 213 return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") 214 } 215 logger.Instance.WriteRequest(r, logger.Filter{ 216 Header: func(k string, v []string) (bool, []string) { 217 // remove the auth token from the log 218 if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") { 219 v = []string{"**REDACTED**"} 220 } 221 return true, v 222 }, 223 }) 224 resp, err := SendWithSender(c.sender(), r) 225 logger.Instance.WriteResponse(resp, logger.Filter{}) 226 Respond(resp, c.ByInspecting()) 227 return resp, err 228} 229 230// sender returns the Sender to which to send requests. 231func (c Client) sender() Sender { 232 if c.Sender == nil { 233 // Use behaviour compatible with DefaultTransport, but require TLS minimum version. 234 var defaultTransport = http.DefaultTransport.(*http.Transport) 235 236 tracing.Transport.Base = &http.Transport{ 237 Proxy: defaultTransport.Proxy, 238 DialContext: defaultTransport.DialContext, 239 MaxIdleConns: defaultTransport.MaxIdleConns, 240 IdleConnTimeout: defaultTransport.IdleConnTimeout, 241 TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, 242 ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, 243 TLSClientConfig: &tls.Config{ 244 MinVersion: tls.VersionTLS12, 245 }, 246 } 247 248 j, _ := cookiejar.New(nil) 249 return &http.Client{Jar: j, Transport: tracing.Transport} 250 } 251 252 return c.Sender 253} 254 255// WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator 256// from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer. 257func (c Client) WithAuthorization() PrepareDecorator { 258 return c.authorizer().WithAuthorization() 259} 260 261// authorizer returns the Authorizer to use. 262func (c Client) authorizer() Authorizer { 263 if c.Authorizer == nil { 264 return NullAuthorizer{} 265 } 266 return c.Authorizer 267} 268 269// WithInspection is a convenience method that passes the request to the supplied RequestInspector, 270// if present, or returns the WithNothing PrepareDecorator otherwise. 271func (c Client) WithInspection() PrepareDecorator { 272 if c.RequestInspector == nil { 273 return WithNothing() 274 } 275 return c.RequestInspector 276} 277 278// ByInspecting is a convenience method that passes the response to the supplied ResponseInspector, 279// if present, or returns the ByIgnoring RespondDecorator otherwise. 280func (c Client) ByInspecting() RespondDecorator { 281 if c.ResponseInspector == nil { 282 return ByIgnoring() 283 } 284 return c.ResponseInspector 285} 286