1package autorest
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"log"
9	"net/http"
10	"net/http/cookiejar"
11	"time"
12)
13
14const (
15	// DefaultPollingDelay is a reasonable delay between polling requests.
16	DefaultPollingDelay = 60 * time.Second
17
18	// DefaultPollingDuration is a reasonable total polling duration.
19	DefaultPollingDuration = 15 * time.Minute
20
21	// DefaultRetryAttempts is number of attempts for retry status codes (5xx).
22	DefaultRetryAttempts = 3
23)
24
25var statusCodesForRetry = []int{
26	http.StatusRequestTimeout,      // 408
27	http.StatusInternalServerError, // 500
28	http.StatusBadGateway,          // 502
29	http.StatusServiceUnavailable,  // 503
30	http.StatusGatewayTimeout,      // 504
31}
32
33const (
34	requestFormat = `HTTP Request Begin ===================================================
35%s
36===================================================== HTTP Request End
37`
38	responseFormat = `HTTP Response Begin ===================================================
39%s
40===================================================== HTTP Response End
41`
42)
43
44// Response serves as the base for all responses from generated clients. It provides access to the
45// last http.Response.
46type Response struct {
47	*http.Response `json:"-"`
48}
49
50// LoggingInspector implements request and response inspectors that log the full request and
51// response to a supplied log.
52type LoggingInspector struct {
53	Logger *log.Logger
54}
55
56// WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The
57// body is restored after being emitted.
58//
59// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
60// important. It is best used to trace JSON or similar body values.
61func (li LoggingInspector) WithInspection() PrepareDecorator {
62	return func(p Preparer) Preparer {
63		return PreparerFunc(func(r *http.Request) (*http.Request, error) {
64			var body, b bytes.Buffer
65
66			defer r.Body.Close()
67
68			r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body))
69			if err := r.Write(&b); err != nil {
70				return nil, fmt.Errorf("Failed to write response: %v", err)
71			}
72
73			li.Logger.Printf(requestFormat, b.String())
74
75			r.Body = ioutil.NopCloser(&body)
76			return p.Prepare(r)
77		})
78	}
79}
80
81// ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
82// body is restored after being emitted.
83//
84// Note: Since it reads the entire Body, this decorator should not be used where body streaming is
85// important. It is best used to trace JSON or similar body values.
86func (li LoggingInspector) ByInspecting() RespondDecorator {
87	return func(r Responder) Responder {
88		return ResponderFunc(func(resp *http.Response) error {
89			var body, b bytes.Buffer
90			defer resp.Body.Close()
91			resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body))
92			if err := resp.Write(&b); err != nil {
93				return fmt.Errorf("Failed to write response: %v", err)
94			}
95
96			li.Logger.Printf(responseFormat, b.String())
97
98			resp.Body = ioutil.NopCloser(&body)
99			return r.Respond(resp)
100		})
101	}
102}
103
104// Client is the base for autorest generated clients. It provides default, "do nothing"
105// implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
106// standard, undecorated http.Client as a default Sender.
107//
108// Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
109// return responses that compose with Response.
110//
111// Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
112// RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
113// breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
114// sending the request by providing a decorated Sender.
115type Client struct {
116	Authorizer        Authorizer
117	Sender            Sender
118	RequestInspector  PrepareDecorator
119	ResponseInspector RespondDecorator
120
121	// PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header
122	PollingDelay time.Duration
123
124	// PollingDuration sets the maximum polling time after which an error is returned.
125	PollingDuration time.Duration
126
127	// RetryAttempts sets the default number of retry attempts for client.
128	RetryAttempts int
129
130	// RetryDuration sets the delay duration for retries.
131	RetryDuration time.Duration
132
133	// UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
134	// through the Do method.
135	UserAgent string
136
137	Jar http.CookieJar
138}
139
140// NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
141// string.
142func NewClientWithUserAgent(ua string) Client {
143	return Client{
144		PollingDelay:    DefaultPollingDelay,
145		PollingDuration: DefaultPollingDuration,
146		RetryAttempts:   DefaultRetryAttempts,
147		RetryDuration:   30 * time.Second,
148		UserAgent:       ua,
149	}
150}
151
152// Do implements the Sender interface by invoking the active Sender after applying authorization.
153// If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent
154// is set, apply set the User-Agent header.
155func (c Client) Do(r *http.Request) (*http.Response, error) {
156	if r.UserAgent() == "" {
157		r, _ = Prepare(r,
158			WithUserAgent(c.UserAgent))
159	}
160	r, err := Prepare(r,
161		c.WithInspection(),
162		c.WithAuthorization())
163	if err != nil {
164		return nil, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
165	}
166	resp, err := SendWithSender(c.sender(), r,
167		DoRetryForStatusCodes(c.RetryAttempts, c.RetryDuration, statusCodesForRetry...))
168	Respond(resp,
169		c.ByInspecting())
170	return resp, err
171}
172
173// sender returns the Sender to which to send requests.
174func (c Client) sender() Sender {
175	if c.Sender == nil {
176		j, _ := cookiejar.New(nil)
177		return &http.Client{Jar: j}
178	}
179	return c.Sender
180}
181
182// WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
183// from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
184func (c Client) WithAuthorization() PrepareDecorator {
185	return c.authorizer().WithAuthorization()
186}
187
188// authorizer returns the Authorizer to use.
189func (c Client) authorizer() Authorizer {
190	if c.Authorizer == nil {
191		return NullAuthorizer{}
192	}
193	return c.Authorizer
194}
195
196// WithInspection is a convenience method that passes the request to the supplied RequestInspector,
197// if present, or returns the WithNothing PrepareDecorator otherwise.
198func (c Client) WithInspection() PrepareDecorator {
199	if c.RequestInspector == nil {
200		return WithNothing()
201	}
202	return c.RequestInspector
203}
204
205// ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
206// if present, or returns the ByIgnoring RespondDecorator otherwise.
207func (c Client) ByInspecting() RespondDecorator {
208	if c.ResponseInspector == nil {
209		return ByIgnoring()
210	}
211	return c.ResponseInspector
212}
213