dial.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright 2015 Google LLC.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Package http supports network connections to HTTP servers.
  5. // This package is not intended for use by end developers. Use the
  6. // google.golang.org/api/option package to configure API clients.
  7. package http
  8. import (
  9. "context"
  10. "crypto/tls"
  11. "errors"
  12. "net"
  13. "net/http"
  14. "time"
  15. "go.opencensus.io/plugin/ochttp"
  16. "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
  17. "golang.org/x/net/http2"
  18. "golang.org/x/oauth2"
  19. "google.golang.org/api/googleapi/transport"
  20. "google.golang.org/api/internal"
  21. "google.golang.org/api/internal/cert"
  22. "google.golang.org/api/option"
  23. "google.golang.org/api/transport/http/internal/propagation"
  24. )
  25. // NewClient returns an HTTP client for use communicating with a Google cloud
  26. // service, configured with the given ClientOptions. It also returns the endpoint
  27. // for the service as specified in the options.
  28. func NewClient(ctx context.Context, opts ...option.ClientOption) (*http.Client, string, error) {
  29. settings, err := newSettings(opts)
  30. if err != nil {
  31. return nil, "", err
  32. }
  33. clientCertSource, dialTLSContext, endpoint, err := internal.GetHTTPTransportConfigAndEndpoint(settings)
  34. if err != nil {
  35. return nil, "", err
  36. }
  37. // TODO(cbro): consider injecting the User-Agent even if an explicit HTTP client is provided?
  38. if settings.HTTPClient != nil {
  39. return settings.HTTPClient, endpoint, nil
  40. }
  41. trans, err := newTransport(ctx, defaultBaseTransport(ctx, clientCertSource, dialTLSContext), settings)
  42. if err != nil {
  43. return nil, "", err
  44. }
  45. return &http.Client{Transport: trans}, endpoint, nil
  46. }
  47. // NewTransport creates an http.RoundTripper for use communicating with a Google
  48. // cloud service, configured with the given ClientOptions. Its RoundTrip method delegates to base.
  49. func NewTransport(ctx context.Context, base http.RoundTripper, opts ...option.ClientOption) (http.RoundTripper, error) {
  50. settings, err := newSettings(opts)
  51. if err != nil {
  52. return nil, err
  53. }
  54. if settings.HTTPClient != nil {
  55. return nil, errors.New("transport/http: WithHTTPClient passed to NewTransport")
  56. }
  57. return newTransport(ctx, base, settings)
  58. }
  59. func newTransport(ctx context.Context, base http.RoundTripper, settings *internal.DialSettings) (http.RoundTripper, error) {
  60. paramTransport := &parameterTransport{
  61. base: base,
  62. userAgent: settings.UserAgent,
  63. requestReason: settings.RequestReason,
  64. }
  65. var trans http.RoundTripper = paramTransport
  66. // Give OpenTelemetry precedence over OpenCensus in case user configuration
  67. // causes both to write the same header (`X-Cloud-Trace-Context`).
  68. trans = addOpenTelemetryTransport(trans, settings)
  69. trans = addOCTransport(trans, settings)
  70. switch {
  71. case settings.NoAuth:
  72. // Do nothing.
  73. case settings.APIKey != "":
  74. paramTransport.quotaProject = internal.GetQuotaProject(nil, settings.QuotaProject)
  75. trans = &transport.APIKey{
  76. Transport: trans,
  77. Key: settings.APIKey,
  78. }
  79. default:
  80. creds, err := internal.Creds(ctx, settings)
  81. if err != nil {
  82. return nil, err
  83. }
  84. if settings.TokenSource == nil {
  85. // We only validate non-tokensource creds, as TokenSource-based credentials
  86. // don't propagate universe.
  87. credsUniverseDomain, err := internal.GetUniverseDomain(creds)
  88. if err != nil {
  89. return nil, err
  90. }
  91. if settings.GetUniverseDomain() != credsUniverseDomain {
  92. return nil, internal.ErrUniverseNotMatch(settings.GetUniverseDomain(), credsUniverseDomain)
  93. }
  94. }
  95. paramTransport.quotaProject = internal.GetQuotaProject(creds, settings.QuotaProject)
  96. ts := creds.TokenSource
  97. if settings.ImpersonationConfig == nil && settings.TokenSource != nil {
  98. ts = settings.TokenSource
  99. }
  100. trans = &oauth2.Transport{
  101. Base: trans,
  102. Source: ts,
  103. }
  104. }
  105. return trans, nil
  106. }
  107. func newSettings(opts []option.ClientOption) (*internal.DialSettings, error) {
  108. var o internal.DialSettings
  109. for _, opt := range opts {
  110. opt.Apply(&o)
  111. }
  112. if err := o.Validate(); err != nil {
  113. return nil, err
  114. }
  115. if o.GRPCConn != nil {
  116. return nil, errors.New("unsupported gRPC connection specified")
  117. }
  118. return &o, nil
  119. }
  120. type parameterTransport struct {
  121. userAgent string
  122. quotaProject string
  123. requestReason string
  124. base http.RoundTripper
  125. }
  126. func (t *parameterTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  127. rt := t.base
  128. if rt == nil {
  129. return nil, errors.New("transport: no Transport specified")
  130. }
  131. newReq := *req
  132. newReq.Header = make(http.Header)
  133. for k, vv := range req.Header {
  134. newReq.Header[k] = vv
  135. }
  136. if t.userAgent != "" {
  137. // TODO(cbro): append to existing User-Agent header?
  138. newReq.Header.Set("User-Agent", t.userAgent)
  139. }
  140. // Attach system parameters into the header
  141. if t.quotaProject != "" {
  142. newReq.Header.Set("X-Goog-User-Project", t.quotaProject)
  143. }
  144. if t.requestReason != "" {
  145. newReq.Header.Set("X-Goog-Request-Reason", t.requestReason)
  146. }
  147. return rt.RoundTrip(&newReq)
  148. }
  149. // defaultBaseTransport returns the base HTTP transport. It uses a default
  150. // transport, taking most defaults from http.DefaultTransport.
  151. // If TLSCertificate is available, set TLSClientConfig as well.
  152. func defaultBaseTransport(ctx context.Context, clientCertSource cert.Source, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
  153. // Copy http.DefaultTransport except for MaxIdleConnsPerHost setting,
  154. // which is increased due to reported performance issues under load in the
  155. // GCS client. Transport.Clone is only available in Go 1.13 and up.
  156. trans := clonedTransport(http.DefaultTransport)
  157. if trans == nil {
  158. trans = fallbackBaseTransport()
  159. }
  160. trans.MaxIdleConnsPerHost = 100
  161. if clientCertSource != nil {
  162. trans.TLSClientConfig = &tls.Config{
  163. GetClientCertificate: clientCertSource,
  164. }
  165. }
  166. if dialTLSContext != nil {
  167. // If DialTLSContext is set, TLSClientConfig wil be ignored
  168. trans.DialTLSContext = dialTLSContext
  169. }
  170. configureHTTP2(trans)
  171. return trans
  172. }
  173. // configureHTTP2 configures the ReadIdleTimeout HTTP/2 option for the
  174. // transport. This allows broken idle connections to be pruned more quickly,
  175. // preventing the client from attempting to re-use connections that will no
  176. // longer work.
  177. func configureHTTP2(trans *http.Transport) {
  178. http2Trans, err := http2.ConfigureTransports(trans)
  179. if err == nil {
  180. http2Trans.ReadIdleTimeout = time.Second * 31
  181. }
  182. }
  183. // fallbackBaseTransport is used in <go1.13 as well as in the rare case if
  184. // http.DefaultTransport has been reassigned something that's not a
  185. // *http.Transport.
  186. func fallbackBaseTransport() *http.Transport {
  187. return &http.Transport{
  188. Proxy: http.ProxyFromEnvironment,
  189. DialContext: (&net.Dialer{
  190. Timeout: 30 * time.Second,
  191. KeepAlive: 30 * time.Second,
  192. DualStack: true,
  193. }).DialContext,
  194. MaxIdleConns: 100,
  195. MaxIdleConnsPerHost: 100,
  196. IdleConnTimeout: 90 * time.Second,
  197. TLSHandshakeTimeout: 10 * time.Second,
  198. ExpectContinueTimeout: 1 * time.Second,
  199. }
  200. }
  201. func addOpenTelemetryTransport(trans http.RoundTripper, settings *internal.DialSettings) http.RoundTripper {
  202. if settings.TelemetryDisabled {
  203. return trans
  204. }
  205. return otelhttp.NewTransport(trans)
  206. }
  207. func addOCTransport(trans http.RoundTripper, settings *internal.DialSettings) http.RoundTripper {
  208. if settings.TelemetryDisabled {
  209. return trans
  210. }
  211. return &ochttp.Transport{
  212. Base: trans,
  213. Propagation: &propagation.HTTPFormat{},
  214. }
  215. }
  216. // clonedTransport returns the given RoundTripper as a cloned *http.Transport.
  217. // It returns nil if the RoundTripper can't be cloned or coerced to
  218. // *http.Transport.
  219. func clonedTransport(rt http.RoundTripper) *http.Transport {
  220. t, ok := rt.(*http.Transport)
  221. if !ok {
  222. return nil
  223. }
  224. return t.Clone()
  225. }