123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- package autorest
- // Copyright 2017 Microsoft Corporation
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "net/http/cookiejar"
- "strings"
- "time"
- "github.com/Azure/go-autorest/logger"
- "github.com/Azure/go-autorest/version"
- )
- const (
- // DefaultPollingDelay is a reasonable delay between polling requests.
- DefaultPollingDelay = 60 * time.Second
- // DefaultPollingDuration is a reasonable total polling duration.
- DefaultPollingDuration = 15 * time.Minute
- // DefaultRetryAttempts is number of attempts for retry status codes (5xx).
- DefaultRetryAttempts = 3
- // DefaultRetryDuration is the duration to wait between retries.
- DefaultRetryDuration = 30 * time.Second
- )
- var (
- // StatusCodesForRetry are a defined group of status code for which the client will retry
- StatusCodesForRetry = []int{
- http.StatusRequestTimeout, // 408
- http.StatusTooManyRequests, // 429
- http.StatusInternalServerError, // 500
- http.StatusBadGateway, // 502
- http.StatusServiceUnavailable, // 503
- http.StatusGatewayTimeout, // 504
- }
- )
- const (
- requestFormat = `HTTP Request Begin ===================================================
- %s
- ===================================================== HTTP Request End
- `
- responseFormat = `HTTP Response Begin ===================================================
- %s
- ===================================================== HTTP Response End
- `
- )
- // Response serves as the base for all responses from generated clients. It provides access to the
- // last http.Response.
- type Response struct {
- *http.Response `json:"-"`
- }
- // LoggingInspector implements request and response inspectors that log the full request and
- // response to a supplied log.
- type LoggingInspector struct {
- Logger *log.Logger
- }
- // WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The
- // body is restored after being emitted.
- //
- // Note: Since it reads the entire Body, this decorator should not be used where body streaming is
- // important. It is best used to trace JSON or similar body values.
- func (li LoggingInspector) WithInspection() PrepareDecorator {
- return func(p Preparer) Preparer {
- return PreparerFunc(func(r *http.Request) (*http.Request, error) {
- var body, b bytes.Buffer
- defer r.Body.Close()
- r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body))
- if err := r.Write(&b); err != nil {
- return nil, fmt.Errorf("Failed to write response: %v", err)
- }
- li.Logger.Printf(requestFormat, b.String())
- r.Body = ioutil.NopCloser(&body)
- return p.Prepare(r)
- })
- }
- }
- // ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
- // body is restored after being emitted.
- //
- // Note: Since it reads the entire Body, this decorator should not be used where body streaming is
- // important. It is best used to trace JSON or similar body values.
- func (li LoggingInspector) ByInspecting() RespondDecorator {
- return func(r Responder) Responder {
- return ResponderFunc(func(resp *http.Response) error {
- var body, b bytes.Buffer
- defer resp.Body.Close()
- resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body))
- if err := resp.Write(&b); err != nil {
- return fmt.Errorf("Failed to write response: %v", err)
- }
- li.Logger.Printf(responseFormat, b.String())
- resp.Body = ioutil.NopCloser(&body)
- return r.Respond(resp)
- })
- }
- }
- // Client is the base for autorest generated clients. It provides default, "do nothing"
- // implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
- // standard, undecorated http.Client as a default Sender.
- //
- // Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
- // return responses that compose with Response.
- //
- // Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
- // RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
- // breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
- // sending the request by providing a decorated Sender.
- type Client struct {
- Authorizer Authorizer
- Sender Sender
- RequestInspector PrepareDecorator
- ResponseInspector RespondDecorator
- // PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header
- PollingDelay time.Duration
- // PollingDuration sets the maximum polling time after which an error is returned.
- // Setting this to zero will use the provided context to control the duration.
- PollingDuration time.Duration
- // RetryAttempts sets the default number of retry attempts for client.
- RetryAttempts int
- // RetryDuration sets the delay duration for retries.
- RetryDuration time.Duration
- // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
- // through the Do method.
- UserAgent string
- Jar http.CookieJar
- // Set to true to skip attempted registration of resource providers (false by default).
- SkipResourceProviderRegistration bool
- }
- // NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
- // string.
- func NewClientWithUserAgent(ua string) Client {
- c := Client{
- PollingDelay: DefaultPollingDelay,
- PollingDuration: DefaultPollingDuration,
- RetryAttempts: DefaultRetryAttempts,
- RetryDuration: DefaultRetryDuration,
- UserAgent: version.UserAgent(),
- }
- c.Sender = c.sender()
- c.AddToUserAgent(ua)
- return c
- }
- // AddToUserAgent adds an extension to the current user agent
- func (c *Client) AddToUserAgent(extension string) error {
- if extension != "" {
- c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension)
- return nil
- }
- return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent)
- }
- // Do implements the Sender interface by invoking the active Sender after applying authorization.
- // If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent
- // is set, apply set the User-Agent header.
- func (c Client) Do(r *http.Request) (*http.Response, error) {
- if r.UserAgent() == "" {
- r, _ = Prepare(r,
- WithUserAgent(c.UserAgent))
- }
- // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations
- r, err := Prepare(r,
- c.WithAuthorization(),
- c.WithInspection())
- if err != nil {
- var resp *http.Response
- if detErr, ok := err.(DetailedError); ok {
- // if the authorization failed (e.g. invalid credentials) there will
- // be a response associated with the error, be sure to return it.
- resp = detErr.Response
- }
- return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
- }
- logger.Instance.WriteRequest(r, logger.Filter{
- Header: func(k string, v []string) (bool, []string) {
- // remove the auth token from the log
- if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") {
- v = []string{"**REDACTED**"}
- }
- return true, v
- },
- })
- resp, err := SendWithSender(c.sender(), r)
- logger.Instance.WriteResponse(resp, logger.Filter{})
- Respond(resp, c.ByInspecting())
- return resp, err
- }
- // sender returns the Sender to which to send requests.
- func (c Client) sender() Sender {
- if c.Sender == nil {
- j, _ := cookiejar.New(nil)
- return &http.Client{Jar: j}
- }
- return c.Sender
- }
- // WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
- // from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
- func (c Client) WithAuthorization() PrepareDecorator {
- return c.authorizer().WithAuthorization()
- }
- // authorizer returns the Authorizer to use.
- func (c Client) authorizer() Authorizer {
- if c.Authorizer == nil {
- return NullAuthorizer{}
- }
- return c.Authorizer
- }
- // WithInspection is a convenience method that passes the request to the supplied RequestInspector,
- // if present, or returns the WithNothing PrepareDecorator otherwise.
- func (c Client) WithInspection() PrepareDecorator {
- if c.RequestInspector == nil {
- return WithNothing()
- }
- return c.RequestInspector
- }
- // ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
- // if present, or returns the ByIgnoring RespondDecorator otherwise.
- func (c Client) ByInspecting() RespondDecorator {
- if c.ResponseInspector == nil {
- return ByIgnoring()
- }
- return c.ResponseInspector
- }
|