send.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright 2016 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gensupport
  5. import (
  6. "context"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "net/http"
  11. "strings"
  12. "time"
  13. "github.com/google/uuid"
  14. "github.com/googleapis/gax-go/v2"
  15. "github.com/googleapis/gax-go/v2/callctx"
  16. )
  17. // Use this error type to return an error which allows introspection of both
  18. // the context error and the error from the service.
  19. type wrappedCallErr struct {
  20. ctxErr error
  21. wrappedErr error
  22. }
  23. func (e wrappedCallErr) Error() string {
  24. return fmt.Sprintf("retry failed with %v; last error: %v", e.ctxErr, e.wrappedErr)
  25. }
  26. func (e wrappedCallErr) Unwrap() error {
  27. return e.wrappedErr
  28. }
  29. // Is allows errors.Is to match the error from the call as well as context
  30. // sentinel errors.
  31. func (e wrappedCallErr) Is(target error) bool {
  32. return errors.Is(e.ctxErr, target) || errors.Is(e.wrappedErr, target)
  33. }
  34. // SendRequest sends a single HTTP request using the given client.
  35. // If ctx is non-nil, it calls all hooks, then sends the request with
  36. // req.WithContext, then calls any functions returned by the hooks in
  37. // reverse order.
  38. func SendRequest(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
  39. // Add headers set in context metadata.
  40. if ctx != nil {
  41. headers := callctx.HeadersFromContext(ctx)
  42. for k, vals := range headers {
  43. for _, v := range vals {
  44. req.Header.Add(k, v)
  45. }
  46. }
  47. }
  48. // Disallow Accept-Encoding because it interferes with the automatic gzip handling
  49. // done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
  50. if _, ok := req.Header["Accept-Encoding"]; ok {
  51. return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
  52. }
  53. if ctx == nil {
  54. return client.Do(req)
  55. }
  56. return send(ctx, client, req)
  57. }
  58. func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
  59. if client == nil {
  60. client = http.DefaultClient
  61. }
  62. resp, err := client.Do(req.WithContext(ctx))
  63. // If we got an error, and the context has been canceled,
  64. // the context's error is probably more useful.
  65. if err != nil {
  66. select {
  67. case <-ctx.Done():
  68. err = ctx.Err()
  69. default:
  70. }
  71. }
  72. return resp, err
  73. }
  74. // SendRequestWithRetry sends a single HTTP request using the given client,
  75. // with retries if a retryable error is returned.
  76. // If ctx is non-nil, it calls all hooks, then sends the request with
  77. // req.WithContext, then calls any functions returned by the hooks in
  78. // reverse order.
  79. func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
  80. // Add headers set in context metadata.
  81. if ctx != nil {
  82. headers := callctx.HeadersFromContext(ctx)
  83. for k, vals := range headers {
  84. for _, v := range vals {
  85. req.Header.Add(k, v)
  86. }
  87. }
  88. }
  89. // Disallow Accept-Encoding because it interferes with the automatic gzip handling
  90. // done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
  91. if _, ok := req.Header["Accept-Encoding"]; ok {
  92. return nil, errors.New("google api: custom Accept-Encoding headers not allowed")
  93. }
  94. if ctx == nil {
  95. return client.Do(req)
  96. }
  97. return sendAndRetry(ctx, client, req, retry)
  98. }
  99. func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
  100. if client == nil {
  101. client = http.DefaultClient
  102. }
  103. var resp *http.Response
  104. var err error
  105. attempts := 1
  106. invocationID := uuid.New().String()
  107. baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
  108. // Loop to retry the request, up to the context deadline.
  109. var pause time.Duration
  110. var bo Backoff
  111. if retry != nil && retry.Backoff != nil {
  112. bo = &gax.Backoff{
  113. Initial: retry.Backoff.Initial,
  114. Max: retry.Backoff.Max,
  115. Multiplier: retry.Backoff.Multiplier,
  116. }
  117. } else {
  118. bo = backoff()
  119. }
  120. var errorFunc = retry.errorFunc()
  121. for {
  122. t := time.NewTimer(pause)
  123. select {
  124. case <-ctx.Done():
  125. t.Stop()
  126. // If we got an error and the context has been canceled, return an error acknowledging
  127. // both the context cancelation and the service error.
  128. if err != nil {
  129. return resp, wrappedCallErr{ctx.Err(), err}
  130. }
  131. return resp, ctx.Err()
  132. case <-t.C:
  133. }
  134. if ctx.Err() != nil {
  135. // Check for context cancellation once more. If more than one case in a
  136. // select is satisfied at the same time, Go will choose one arbitrarily.
  137. // That can cause an operation to go through even if the context was
  138. // canceled before.
  139. if err != nil {
  140. return resp, wrappedCallErr{ctx.Err(), err}
  141. }
  142. return resp, ctx.Err()
  143. }
  144. // Set retry metrics and idempotency headers for GCS.
  145. // TODO(b/274504690): Consider dropping gccl-invocation-id key since it
  146. // duplicates the X-Goog-Gcs-Idempotency-Token header (added in v0.115.0).
  147. invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", invocationID, attempts)
  148. xGoogHeader := strings.Join([]string{invocationHeader, baseXGoogHeader}, " ")
  149. req.Header.Set("X-Goog-Api-Client", xGoogHeader)
  150. req.Header.Set("X-Goog-Gcs-Idempotency-Token", invocationID)
  151. resp, err = client.Do(req.WithContext(ctx))
  152. var status int
  153. if resp != nil {
  154. status = resp.StatusCode
  155. }
  156. // Check if we can retry the request. A retry can only be done if the error
  157. // is retryable and the request body can be re-created using GetBody (this
  158. // will not be possible if the body was unbuffered).
  159. if req.GetBody == nil || !errorFunc(status, err) {
  160. break
  161. }
  162. attempts++
  163. var errBody error
  164. req.Body, errBody = req.GetBody()
  165. if errBody != nil {
  166. break
  167. }
  168. pause = bo.Pause()
  169. if resp != nil && resp.Body != nil {
  170. resp.Body.Close()
  171. }
  172. }
  173. return resp, err
  174. }
  175. // DecodeResponse decodes the body of res into target. If there is no body,
  176. // target is unchanged.
  177. func DecodeResponse(target interface{}, res *http.Response) error {
  178. if res.StatusCode == http.StatusNoContent {
  179. return nil
  180. }
  181. return json.NewDecoder(res.Body).Decode(target)
  182. }