1
2
3
4
5
6
7
8 package http
9
10 import (
11 "context"
12 "crypto/tls"
13 "errors"
14 "net"
15 "net/http"
16 "time"
17
18 "cloud.google.com/go/auth"
19 "cloud.google.com/go/auth/credentials"
20 "cloud.google.com/go/auth/httptransport"
21 "cloud.google.com/go/auth/oauth2adapt"
22 "go.opencensus.io/plugin/ochttp"
23 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
24 "golang.org/x/net/http2"
25 "golang.org/x/oauth2"
26 "google.golang.org/api/googleapi/transport"
27 "google.golang.org/api/internal"
28 "google.golang.org/api/internal/cert"
29 "google.golang.org/api/option"
30 "google.golang.org/api/transport/http/internal/propagation"
31 )
32
33
34
35
36 func NewClient(ctx context.Context, opts ...option.ClientOption) (*http.Client, string, error) {
37 settings, err := newSettings(opts)
38 if err != nil {
39 return nil, "", err
40 }
41 clientCertSource, dialTLSContext, endpoint, err := internal.GetHTTPTransportConfigAndEndpoint(settings)
42 if err != nil {
43 return nil, "", err
44 }
45
46 if settings.HTTPClient != nil {
47 return settings.HTTPClient, endpoint, nil
48 }
49
50 if settings.IsNewAuthLibraryEnabled() {
51 client, err := newClientNewAuth(ctx, nil, settings)
52 if err != nil {
53 return nil, "", err
54 }
55 return client, endpoint, nil
56 }
57 trans, err := newTransport(ctx, defaultBaseTransport(ctx, clientCertSource, dialTLSContext), settings)
58 if err != nil {
59 return nil, "", err
60 }
61 return &http.Client{Transport: trans}, endpoint, nil
62 }
63
64
65 func newClientNewAuth(ctx context.Context, base http.RoundTripper, ds *internal.DialSettings) (*http.Client, error) {
66
67 var creds *auth.Credentials
68 if ds.InternalCredentials != nil {
69 creds = oauth2adapt.AuthCredentialsFromOauth2Credentials(ds.InternalCredentials)
70 } else if ds.Credentials != nil {
71 creds = oauth2adapt.AuthCredentialsFromOauth2Credentials(ds.Credentials)
72 } else if ds.AuthCredentials != nil {
73 creds = ds.AuthCredentials
74 } else if ds.TokenSource != nil {
75 credOpts := &auth.CredentialsOptions{
76 TokenProvider: oauth2adapt.TokenProviderFromTokenSource(ds.TokenSource),
77 }
78 if ds.QuotaProject != "" {
79 credOpts.QuotaProjectIDProvider = auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
80 return ds.QuotaProject, nil
81 })
82 }
83 creds = auth.NewCredentials(credOpts)
84 }
85
86 var skipValidation bool
87
88
89 if ds.SkipValidation || ds.InternalCredentials != nil {
90 skipValidation = true
91 }
92
93
94 defaultEndpointTemplate := ds.DefaultEndpointTemplate
95 if defaultEndpointTemplate == "" {
96 defaultEndpointTemplate = ds.DefaultEndpoint
97 }
98
99 var aud string
100 if len(ds.Audiences) > 0 {
101 aud = ds.Audiences[0]
102 }
103 headers := http.Header{}
104 if ds.QuotaProject != "" {
105 headers.Set("X-goog-user-project", ds.QuotaProject)
106 }
107 if ds.RequestReason != "" {
108 headers.Set("X-goog-request-reason", ds.RequestReason)
109 }
110 client, err := httptransport.NewClient(&httptransport.Options{
111 DisableTelemetry: ds.TelemetryDisabled,
112 DisableAuthentication: ds.NoAuth,
113 Headers: headers,
114 Endpoint: ds.Endpoint,
115 APIKey: ds.APIKey,
116 Credentials: creds,
117 ClientCertProvider: ds.ClientCertSource,
118 BaseRoundTripper: base,
119 DetectOpts: &credentials.DetectOptions{
120 Scopes: ds.Scopes,
121 Audience: aud,
122 CredentialsFile: ds.CredentialsFile,
123 CredentialsJSON: ds.CredentialsJSON,
124 Client: oauth2.NewClient(ctx, nil),
125 },
126 InternalOptions: &httptransport.InternalOptions{
127 EnableJWTWithScope: ds.EnableJwtWithScope,
128 DefaultAudience: ds.DefaultAudience,
129 DefaultEndpointTemplate: defaultEndpointTemplate,
130 DefaultMTLSEndpoint: ds.DefaultMTLSEndpoint,
131 DefaultScopes: ds.DefaultScopes,
132 SkipValidation: skipValidation,
133 },
134 })
135 if err != nil {
136 return nil, err
137 }
138 return client, nil
139 }
140
141
142
143 func NewTransport(ctx context.Context, base http.RoundTripper, opts ...option.ClientOption) (http.RoundTripper, error) {
144 settings, err := newSettings(opts)
145 if err != nil {
146 return nil, err
147 }
148 if settings.HTTPClient != nil {
149 return nil, errors.New("transport/http: WithHTTPClient passed to NewTransport")
150 }
151 if settings.IsNewAuthLibraryEnabled() {
152 client, err := newClientNewAuth(ctx, base, settings)
153 if err != nil {
154 return nil, err
155 }
156 return client.Transport, nil
157 }
158 return newTransport(ctx, base, settings)
159 }
160
161 func newTransport(ctx context.Context, base http.RoundTripper, settings *internal.DialSettings) (http.RoundTripper, error) {
162 paramTransport := ¶meterTransport{
163 base: base,
164 userAgent: settings.UserAgent,
165 requestReason: settings.RequestReason,
166 }
167 var trans http.RoundTripper = paramTransport
168
169
170 trans = addOpenTelemetryTransport(trans, settings)
171 trans = addOCTransport(trans, settings)
172 switch {
173 case settings.NoAuth:
174
175 case settings.APIKey != "":
176 paramTransport.quotaProject = internal.GetQuotaProject(nil, settings.QuotaProject)
177 trans = &transport.APIKey{
178 Transport: trans,
179 Key: settings.APIKey,
180 }
181 default:
182 creds, err := internal.Creds(ctx, settings)
183 if err != nil {
184 return nil, err
185 }
186 if settings.TokenSource == nil {
187
188
189 credsUniverseDomain, err := internal.GetUniverseDomain(creds)
190 if err != nil {
191 return nil, err
192 }
193 if settings.GetUniverseDomain() != credsUniverseDomain {
194 return nil, internal.ErrUniverseNotMatch(settings.GetUniverseDomain(), credsUniverseDomain)
195 }
196 }
197 paramTransport.quotaProject = internal.GetQuotaProject(creds, settings.QuotaProject)
198 ts := creds.TokenSource
199 if settings.ImpersonationConfig == nil && settings.TokenSource != nil {
200 ts = settings.TokenSource
201 }
202 trans = &oauth2.Transport{
203 Base: trans,
204 Source: ts,
205 }
206 }
207 return trans, nil
208 }
209
210 func newSettings(opts []option.ClientOption) (*internal.DialSettings, error) {
211 var o internal.DialSettings
212 for _, opt := range opts {
213 opt.Apply(&o)
214 }
215 if err := o.Validate(); err != nil {
216 return nil, err
217 }
218 if o.GRPCConn != nil {
219 return nil, errors.New("unsupported gRPC connection specified")
220 }
221 return &o, nil
222 }
223
224 type parameterTransport struct {
225 userAgent string
226 quotaProject string
227 requestReason string
228
229 base http.RoundTripper
230 }
231
232 func (t *parameterTransport) RoundTrip(req *http.Request) (*http.Response, error) {
233 rt := t.base
234 if rt == nil {
235 return nil, errors.New("transport: no Transport specified")
236 }
237 newReq := *req
238 newReq.Header = make(http.Header)
239 for k, vv := range req.Header {
240 newReq.Header[k] = vv
241 }
242 if t.userAgent != "" {
243
244 newReq.Header.Set("User-Agent", t.userAgent)
245 }
246
247
248 if t.quotaProject != "" {
249 newReq.Header.Set("X-Goog-User-Project", t.quotaProject)
250 }
251 if t.requestReason != "" {
252 newReq.Header.Set("X-Goog-Request-Reason", t.requestReason)
253 }
254
255 return rt.RoundTrip(&newReq)
256 }
257
258
259
260
261 func defaultBaseTransport(ctx context.Context, clientCertSource cert.Source, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
262
263
264
265 trans := clonedTransport(http.DefaultTransport)
266 if trans == nil {
267 trans = fallbackBaseTransport()
268 }
269 trans.MaxIdleConnsPerHost = 100
270
271 if clientCertSource != nil {
272 trans.TLSClientConfig = &tls.Config{
273 GetClientCertificate: clientCertSource,
274 }
275 }
276 if dialTLSContext != nil {
277
278 trans.DialTLSContext = dialTLSContext
279 }
280
281 configureHTTP2(trans)
282
283 return trans
284 }
285
286
287
288
289
290 func configureHTTP2(trans *http.Transport) {
291 http2Trans, err := http2.ConfigureTransports(trans)
292 if err == nil {
293 http2Trans.ReadIdleTimeout = time.Second * 31
294 }
295 }
296
297
298
299
300 func fallbackBaseTransport() *http.Transport {
301 return &http.Transport{
302 Proxy: http.ProxyFromEnvironment,
303 DialContext: (&net.Dialer{
304 Timeout: 30 * time.Second,
305 KeepAlive: 30 * time.Second,
306 DualStack: true,
307 }).DialContext,
308 MaxIdleConns: 100,
309 MaxIdleConnsPerHost: 100,
310 IdleConnTimeout: 90 * time.Second,
311 TLSHandshakeTimeout: 10 * time.Second,
312 ExpectContinueTimeout: 1 * time.Second,
313 }
314 }
315
316 func addOpenTelemetryTransport(trans http.RoundTripper, settings *internal.DialSettings) http.RoundTripper {
317 if settings.TelemetryDisabled {
318 return trans
319 }
320 return otelhttp.NewTransport(trans)
321 }
322
323 func addOCTransport(trans http.RoundTripper, settings *internal.DialSettings) http.RoundTripper {
324 if settings.TelemetryDisabled {
325 return trans
326 }
327 return &ochttp.Transport{
328 Base: trans,
329 Propagation: &propagation.HTTPFormat{},
330 }
331 }
332
333
334
335
336 func clonedTransport(rt http.RoundTripper) *http.Transport {
337 t, ok := rt.(*http.Transport)
338 if !ok {
339 return nil
340 }
341 return t.Clone()
342 }
343
View as plain text