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