client.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package autorest
  2. // Copyright 2017 Microsoft Corporation
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. import (
  16. "bytes"
  17. "crypto/tls"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "log"
  22. "net/http"
  23. "strings"
  24. "time"
  25. "github.com/Azure/go-autorest/logger"
  26. )
  27. const (
  28. // DefaultPollingDelay is a reasonable delay between polling requests.
  29. DefaultPollingDelay = 60 * time.Second
  30. // DefaultPollingDuration is a reasonable total polling duration.
  31. DefaultPollingDuration = 15 * time.Minute
  32. // DefaultRetryAttempts is number of attempts for retry status codes (5xx).
  33. DefaultRetryAttempts = 3
  34. // DefaultRetryDuration is the duration to wait between retries.
  35. DefaultRetryDuration = 30 * time.Second
  36. )
  37. var (
  38. // StatusCodesForRetry are a defined group of status code for which the client will retry
  39. StatusCodesForRetry = []int{
  40. http.StatusRequestTimeout, // 408
  41. http.StatusTooManyRequests, // 429
  42. http.StatusInternalServerError, // 500
  43. http.StatusBadGateway, // 502
  44. http.StatusServiceUnavailable, // 503
  45. http.StatusGatewayTimeout, // 504
  46. }
  47. )
  48. const (
  49. requestFormat = `HTTP Request Begin ===================================================
  50. %s
  51. ===================================================== HTTP Request End
  52. `
  53. responseFormat = `HTTP Response Begin ===================================================
  54. %s
  55. ===================================================== HTTP Response End
  56. `
  57. )
  58. // Response serves as the base for all responses from generated clients. It provides access to the
  59. // last http.Response.
  60. type Response struct {
  61. *http.Response `json:"-"`
  62. }
  63. // IsHTTPStatus returns true if the returned HTTP status code matches the provided status code.
  64. // If there was no response (i.e. the underlying http.Response is nil) the return value is false.
  65. func (r Response) IsHTTPStatus(statusCode int) bool {
  66. if r.Response == nil {
  67. return false
  68. }
  69. return r.Response.StatusCode == statusCode
  70. }
  71. // HasHTTPStatus returns true if the returned HTTP status code matches one of the provided status codes.
  72. // If there was no response (i.e. the underlying http.Response is nil) or not status codes are provided
  73. // the return value is false.
  74. func (r Response) HasHTTPStatus(statusCodes ...int) bool {
  75. return ResponseHasStatusCode(r.Response, statusCodes...)
  76. }
  77. // LoggingInspector implements request and response inspectors that log the full request and
  78. // response to a supplied log.
  79. type LoggingInspector struct {
  80. Logger *log.Logger
  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.
  87. func (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. defer r.Body.Close()
  92. r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body))
  93. if err := r.Write(&b); err != nil {
  94. return nil, fmt.Errorf("Failed to write response: %v", err)
  95. }
  96. li.Logger.Printf(requestFormat, b.String())
  97. r.Body = ioutil.NopCloser(&body)
  98. return p.Prepare(r)
  99. })
  100. }
  101. }
  102. // ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The
  103. // body is restored after being emitted.
  104. //
  105. // Note: Since it reads the entire Body, this decorator should not be used where body streaming is
  106. // important. It is best used to trace JSON or similar body values.
  107. func (li LoggingInspector) ByInspecting() RespondDecorator {
  108. return func(r Responder) Responder {
  109. return ResponderFunc(func(resp *http.Response) error {
  110. var body, b bytes.Buffer
  111. defer resp.Body.Close()
  112. resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body))
  113. if err := resp.Write(&b); err != nil {
  114. return fmt.Errorf("Failed to write response: %v", err)
  115. }
  116. li.Logger.Printf(responseFormat, b.String())
  117. resp.Body = ioutil.NopCloser(&body)
  118. return r.Respond(resp)
  119. })
  120. }
  121. }
  122. // Client is the base for autorest generated clients. It provides default, "do nothing"
  123. // implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the
  124. // standard, undecorated http.Client as a default Sender.
  125. //
  126. // Generated clients should also use Error (see NewError and NewErrorWithError) for errors and
  127. // return responses that compose with Response.
  128. //
  129. // Most customization of generated clients is best achieved by supplying a custom Authorizer, custom
  130. // RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit
  131. // breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence
  132. // sending the request by providing a decorated Sender.
  133. type Client struct {
  134. Authorizer Authorizer
  135. Sender Sender
  136. RequestInspector PrepareDecorator
  137. ResponseInspector RespondDecorator
  138. // PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header
  139. PollingDelay time.Duration
  140. // PollingDuration sets the maximum polling time after which an error is returned.
  141. // Setting this to zero will use the provided context to control the duration.
  142. PollingDuration time.Duration
  143. // RetryAttempts sets the default number of retry attempts for client.
  144. RetryAttempts int
  145. // RetryDuration sets the delay duration for retries.
  146. RetryDuration time.Duration
  147. // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent
  148. // through the Do method.
  149. UserAgent string
  150. Jar http.CookieJar
  151. // Set to true to skip attempted registration of resource providers (false by default).
  152. SkipResourceProviderRegistration bool
  153. }
  154. // NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed
  155. // string.
  156. func NewClientWithUserAgent(ua string) Client {
  157. return newClient(ua, tls.RenegotiateNever)
  158. }
  159. // ClientOptions contains various Client configuration options.
  160. type ClientOptions struct {
  161. // UserAgent is an optional user-agent string to append to the default user agent.
  162. UserAgent string
  163. // Renegotiation is an optional setting to control client-side TLS renegotiation.
  164. Renegotiation tls.RenegotiationSupport
  165. }
  166. // NewClientWithOptions returns an instance of a Client with the specified values.
  167. func NewClientWithOptions(options ClientOptions) Client {
  168. return newClient(options.UserAgent, options.Renegotiation)
  169. }
  170. func newClient(ua string, renegotiation tls.RenegotiationSupport) Client {
  171. c := Client{
  172. PollingDelay: DefaultPollingDelay,
  173. PollingDuration: DefaultPollingDuration,
  174. RetryAttempts: DefaultRetryAttempts,
  175. RetryDuration: DefaultRetryDuration,
  176. UserAgent: UserAgent(),
  177. }
  178. c.Sender = c.sender(renegotiation)
  179. c.AddToUserAgent(ua)
  180. return c
  181. }
  182. // AddToUserAgent adds an extension to the current user agent
  183. func (c *Client) AddToUserAgent(extension string) error {
  184. if extension != "" {
  185. c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension)
  186. return nil
  187. }
  188. return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent)
  189. }
  190. // Do implements the Sender interface by invoking the active Sender after applying authorization.
  191. // If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent
  192. // is set, apply set the User-Agent header.
  193. func (c Client) Do(r *http.Request) (*http.Response, error) {
  194. if r.UserAgent() == "" {
  195. r, _ = Prepare(r,
  196. WithUserAgent(c.UserAgent))
  197. }
  198. // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations
  199. r, err := Prepare(r,
  200. c.WithAuthorization(),
  201. c.WithInspection())
  202. if err != nil {
  203. var resp *http.Response
  204. if detErr, ok := err.(DetailedError); ok {
  205. // if the authorization failed (e.g. invalid credentials) there will
  206. // be a response associated with the error, be sure to return it.
  207. resp = detErr.Response
  208. }
  209. return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed")
  210. }
  211. logger.Instance.WriteRequest(r, logger.Filter{
  212. Header: func(k string, v []string) (bool, []string) {
  213. // remove the auth token from the log
  214. if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") {
  215. v = []string{"**REDACTED**"}
  216. }
  217. return true, v
  218. },
  219. })
  220. resp, err := SendWithSender(c.sender(tls.RenegotiateNever), r)
  221. logger.Instance.WriteResponse(resp, logger.Filter{})
  222. Respond(resp, c.ByInspecting())
  223. return resp, err
  224. }
  225. // sender returns the Sender to which to send requests.
  226. func (c Client) sender(renengotiation tls.RenegotiationSupport) Sender {
  227. if c.Sender == nil {
  228. return sender(renengotiation)
  229. }
  230. return c.Sender
  231. }
  232. // WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator
  233. // from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer.
  234. func (c Client) WithAuthorization() PrepareDecorator {
  235. return c.authorizer().WithAuthorization()
  236. }
  237. // authorizer returns the Authorizer to use.
  238. func (c Client) authorizer() Authorizer {
  239. if c.Authorizer == nil {
  240. return NullAuthorizer{}
  241. }
  242. return c.Authorizer
  243. }
  244. // WithInspection is a convenience method that passes the request to the supplied RequestInspector,
  245. // if present, or returns the WithNothing PrepareDecorator otherwise.
  246. func (c Client) WithInspection() PrepareDecorator {
  247. if c.RequestInspector == nil {
  248. return WithNothing()
  249. }
  250. return c.RequestInspector
  251. }
  252. // ByInspecting is a convenience method that passes the response to the supplied ResponseInspector,
  253. // if present, or returns the ByIgnoring RespondDecorator otherwise.
  254. func (c Client) ByInspecting() RespondDecorator {
  255. if c.ResponseInspector == nil {
  256. return ByIgnoring()
  257. }
  258. return c.ResponseInspector
  259. }