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