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