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