forked from palantir/conjure-go-runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient_params.go
423 lines (374 loc) · 14.5 KB
/
client_params.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
// Copyright (c) 2018 Palantir Technologies. All rights reserved.
//
// 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.
package httpclient
import (
"context"
"crypto/tls"
"encoding/base64"
"net"
"net/http"
"net/url"
"time"
"github.com/palantir/pkg/bytesbuffers"
"github.com/palantir/pkg/retry"
werror "github.com/palantir/witchcraft-go-error"
"golang.org/x/net/proxy"
)
// ClientParam is a param that can be used to build
type ClientParam interface {
apply(builder *clientBuilder) error
}
type HTTPClientParam interface {
applyHTTPClient(builder *httpClientBuilder) error
}
// ClientOrHTTPClientParam is a param that can be used to build a Client or an http.Client
type ClientOrHTTPClientParam interface {
ClientParam
HTTPClientParam
}
// clientParamFunc is a convenience type that helps build a ClientParam. Use when you want a param that can be used to
// build a Client and *not* an http.Client
type clientParamFunc func(builder *clientBuilder) error
func (f clientParamFunc) apply(b *clientBuilder) error {
return f(b)
}
// httpClientParamFunc is a convenience type that helps build a HTTPClientParam. Use when you want a param that can be used to
// build an http.Client and *not* a Client
type httpClientParamFunc func(builder *httpClientBuilder) error
func (f httpClientParamFunc) applyHTTPClient(b *httpClientBuilder) error {
return f(b)
}
// clientOrHTTPClientParamFunc is a convenience type that helps build a ClientOrHTTPClientParam. Use when you want a param that can be used to
// either as an Client or a http.Client
type clientOrHTTPClientParamFunc func(builder *httpClientBuilder) error
func (f clientOrHTTPClientParamFunc) apply(b *clientBuilder) error {
return f(&b.httpClientBuilder)
}
func (f clientOrHTTPClientParamFunc) applyHTTPClient(b *httpClientBuilder) error {
return f(b)
}
func WithConfig(c ClientConfig) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
params, err := configToParams(c)
if err != nil {
return err
}
for _, p := range params {
if err := p.apply(b); err != nil {
return err
}
}
return nil
})
}
func WithConfigForHTTPClient(c ClientConfig) HTTPClientParam {
return httpClientParamFunc(func(b *httpClientBuilder) error {
params, err := configToParams(c)
if err != nil {
return err
}
for _, p := range params {
httpClientParam, ok := p.(HTTPClientParam)
if !ok {
return werror.Error("param from config was not a http client builder param")
}
if err := httpClientParam.applyHTTPClient(b); err != nil {
return err
}
}
return nil
})
}
func WithServiceName(serviceName string) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.ServiceName = serviceName
return nil
})
}
// WithMiddleware will be invoked for custom HTTP behavior after the
// underlying transport is initialized. Each handler added "wraps" the previous
// round trip, so it will see the request first and the response last.
func WithMiddleware(h Middleware) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.Middlewares = append(b.Middlewares, h)
return nil
})
}
func WithAddHeader(key, value string) ClientOrHTTPClientParam {
return WithMiddleware(MiddlewareFunc(func(req *http.Request, next http.RoundTripper) (*http.Response, error) {
req.Header.Add(key, value)
return next.RoundTrip(req)
}))
}
func WithSetHeader(key, value string) ClientOrHTTPClientParam {
return WithMiddleware(MiddlewareFunc(func(req *http.Request, next http.RoundTripper) (*http.Response, error) {
req.Header.Set(key, value)
return next.RoundTrip(req)
}))
}
// WithAuthToken sets the Authorization header to a static bearerToken.
func WithAuthToken(bearerToken string) ClientOrHTTPClientParam {
return WithAuthTokenProvider(func(context.Context) (string, error) {
return bearerToken, nil
})
}
// WithAuthTokenProvider calls provideToken() and sets the Authorization header.
func WithAuthTokenProvider(provideToken TokenProvider) ClientOrHTTPClientParam {
return WithMiddleware(&authTokenMiddleware{provideToken: provideToken})
}
// WithUserAgent sets the User-Agent header.
func WithUserAgent(userAgent string) ClientOrHTTPClientParam {
return WithSetHeader("User-Agent", userAgent)
}
// WithMetrics enables the "client.response" metric. See MetricsMiddleware for details.
// The serviceName will appear as the "service-name" tag.
func WithMetrics(tagProviders ...TagsProvider) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
m, err := MetricsMiddleware(b.ServiceName, tagProviders...)
if err != nil {
return err
}
b.metricsMiddleware = m
return nil
})
}
// WithBytesBufferPool stores a bytes buffer pool on the client for use in encoding request bodies.
// This prevents allocating a new byte buffer for every request.
func WithBytesBufferPool(pool bytesbuffers.Pool) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.BytesBufferPool = pool
return nil
})
}
// WithDisablePanicRecovery disables the enabled-by-default panic recovery middleware.
// If the request was otherwise succeeding (err == nil), we return a new werror with
// the recovered object as an unsafe param. If there's an error, we werror.Wrap it.
// If errMiddleware is not nil, it is invoked on the recovered object.
func WithDisablePanicRecovery() ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.DisableRecovery = true
return nil
})
}
// WithDisableTracing disables the enabled-by-default tracing middleware which
// instructs the client to propagate trace information using the go-zipkin libraries
// method of attaching traces to requests. The server at the other end of such a request, should
// be instrumented to read zipkin-style headers
//
// If a trace is already attached to a request context, then the trace is continued. Otherwise, no
// trace information is propagate. This will not create a span if one does not exist.
func WithDisableTracing() ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.DisableTracing = true
return nil
})
}
// WithDisableTraceHeaderPropagation disables the enabled-by-default traceId header propagation
// By default, if witchcraft-logging has attached a traceId to the context of the request (for service and request logging),
// then the client will attach this traceId as a header for future services to do the same if desired
func WithDisableTraceHeaderPropagation() ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.disableTraceHeaderPropagation = true
return nil
})
}
// WithHTTPTimeout sets the timeout on the http client.
// If unset, the client defaults to 1 minute.
func WithHTTPTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.Timeout = timeout
return nil
})
}
// WithDisableHTTP2 skips the default behavior of configuring
// the transport with http2.ConfigureTransport.
func WithDisableHTTP2() ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.DisableHTTP2 = true
return nil
})
}
// WithMaxIdleConns sets the number of reusable TCP connections the client
// will maintain. If unset, the client defaults to 32.
func WithMaxIdleConns(conns int) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.MaxIdleConns = conns
return nil
})
}
// WithMaxIdleConnsPerHost sets the number of reusable TCP connections the client
// will maintain per destination. If unset, the client defaults to 32.
func WithMaxIdleConnsPerHost(conns int) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.MaxIdleConnsPerHost = conns
return nil
})
}
// WithNoProxy nils out the Proxy field of the http.Transport,
// ignoring any proxy set in the process's environment.
// If unset, the default is http.ProxyFromEnvironment.
func WithNoProxy() ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.Proxy = nil
return nil
})
}
// WithProxyFromEnvironment can be used to set the HTTP(s) proxy to use
// the Go standard library's http.ProxyFromEnvironment.
func WithProxyFromEnvironment() ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.Proxy = http.ProxyFromEnvironment
return nil
})
}
// WithProxyURL can be used to set a socks5 or HTTP(s) proxy.
func WithProxyURL(proxyURLString string) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
proxyURL, err := url.Parse(proxyURLString)
if err != nil {
return werror.Wrap(err, "failed to parse proxy url")
}
switch proxyURL.Scheme {
case "http", "https":
b.Proxy = http.ProxyURL(proxyURL)
case "socks5":
b.ProxyDialerBuilder = func(dialer *net.Dialer) (proxy.Dialer, error) {
proxyDialer, err := proxy.FromURL(proxyURL, dialer)
if err != nil {
return nil, werror.Wrap(err, "failed to create socks5 dialer")
}
return proxyDialer, nil
}
default:
return werror.Error("unrecognized proxy scheme", werror.SafeParam("scheme", proxyURL.Scheme))
}
return nil
})
}
// WithTLSConfig sets the SSL/TLS configuration for the HTTP client's Transport using a copy of the provided config.
// The palantir/pkg/tlsconfig package is recommended to build a tls.Config from sane defaults.
func WithTLSConfig(conf *tls.Config) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
if conf != nil {
b.TLSClientConfig = conf.Clone()
}
return nil
})
}
// WithDialTimeout sets the timeout on the Dialer.
// If unset, the client defaults to 30 seconds.
func WithDialTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.DialTimeout = timeout
return nil
})
}
// WithIdleConnTimeout sets the timeout for idle connections.
// If unset, the client defaults to 90 seconds.
func WithIdleConnTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.IdleConnTimeout = timeout
return nil
})
}
// WithTLSHandshakeTimeout sets the timeout for TLS handshakes.
// If unset, the client defaults to 10 seconds.
func WithTLSHandshakeTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.TLSHandshakeTimeout = timeout
return nil
})
}
// WithExpectContinueTimeout sets the timeout to receive the server's first response headers after
// fully writing the request headers if the request has an "Expect: 100-continue" header.
// If unset, the client defaults to 1 second.
func WithExpectContinueTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.ExpectContinueTimeout = timeout
return nil
})
}
// WithResponseHeaderTimeout specifies the amount of time to wait for a server's response headers after fully writing
// the request (including its body, if any). This time does not include the time to read the response body. If unset,
// the client defaults to having no response header timeout.
func WithResponseHeaderTimeout(timeout time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.ResponseHeaderTimeout = timeout
return nil
})
}
// WithKeepAlive sets the keep alive frequency on the Dialer.
// If unset, the client defaults to 30 seconds.
func WithKeepAlive(keepAlive time.Duration) ClientOrHTTPClientParam {
return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error {
b.KeepAlive = keepAlive
return nil
})
}
// WithBaseURLs sets the base URLs for every request. This is meant to be used in conjunction with WithPath.
func WithBaseURLs(urls []string) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.uris = urls
return nil
})
}
// WithMaxBackoff sets the maximum backoff between retried calls to the same URI. Defaults to no limit.
func WithMaxBackoff(maxBackoff time.Duration) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.backoffOptions = append(b.backoffOptions, retry.WithMaxBackoff(maxBackoff))
return nil
})
}
// WithInitialBackoff sets the initial backoff between retried calls to the same URI. Defaults to 250ms.
func WithInitialBackoff(initialBackoff time.Duration) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.backoffOptions = append(b.backoffOptions, retry.WithInitialBackoff(initialBackoff))
return nil
})
}
// WithMaxRetries sets the maximum number of retries on transport errors for every request. Backoffs are
// also capped at this.
// If unset, the client defaults to 2 * size of URIs
func WithMaxRetries(maxTransportRetries int) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.maxRetries = maxTransportRetries
return nil
})
}
// WithDisableRestErrors disables the middleware which sets Do()'s returned
// error to a non-nil value in the case of >= 400 HTTP response.
func WithDisableRestErrors() ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.errorDecoder = nil
return nil
})
}
func WithErrorDecoder(errorDecoder ErrorDecoder) ClientParam {
return clientParamFunc(func(b *clientBuilder) error {
b.errorDecoder = errorDecoder
return nil
})
}
// WithBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and
// password.
func WithBasicAuth(username, password string) ClientParam {
return WithMiddleware(MiddlewareFunc(func(req *http.Request, next http.RoundTripper) (*http.Response, error) {
setBasicAuth(req.Header, username, password)
return next.RoundTrip(req)
}))
}
func setBasicAuth(h http.Header, username, password string) {
basicAuthBytes := []byte(username + ":" + password)
h.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(basicAuthBytes))
}