diff --git a/config/zz_generated_refreshables.go b/config/zz_generated_refreshables.go index 4865967d..3cce3cf9 100644 --- a/config/zz_generated_refreshables.go +++ b/config/zz_generated_refreshables.go @@ -331,6 +331,8 @@ type RefreshableClientConfig interface { IdleConnTimeout() refreshable.DurationPtr TLSHandshakeTimeout() refreshable.DurationPtr ExpectContinueTimeout() refreshable.DurationPtr + ResponseHeaderTimeout() refreshable.DurationPtr + KeepAlive() refreshable.DurationPtr HTTP2ReadIdleTimeout() refreshable.DurationPtr HTTP2PingTimeout() refreshable.DurationPtr MaxIdleConns() refreshable.IntPtr @@ -465,6 +467,18 @@ func (r RefreshingClientConfig) ExpectContinueTimeout() refreshable.DurationPtr })) } +func (r RefreshingClientConfig) ResponseHeaderTimeout() refreshable.DurationPtr { + return refreshable.NewDurationPtr(r.MapClientConfig(func(i httpclient.ClientConfig) interface{} { + return i.ResponseHeaderTimeout + })) +} + +func (r RefreshingClientConfig) KeepAlive() refreshable.DurationPtr { + return refreshable.NewDurationPtr(r.MapClientConfig(func(i httpclient.ClientConfig) interface{} { + return i.KeepAlive + })) +} + func (r RefreshingClientConfig) HTTP2ReadIdleTimeout() refreshable.DurationPtr { return refreshable.NewDurationPtr(r.MapClientConfig(func(i httpclient.ClientConfig) interface{} { return i.HTTP2ReadIdleTimeout @@ -679,6 +693,7 @@ type RefreshableSecurityConfig interface { CAFiles() refreshable.StringSlice CertFile() refreshable.String KeyFile() refreshable.String + InsecureSkipVerify() refreshable.BoolPtr } type RefreshingSecurityConfig struct { @@ -723,6 +738,12 @@ func (r RefreshingSecurityConfig) KeyFile() refreshable.String { })) } +func (r RefreshingSecurityConfig) InsecureSkipVerify() refreshable.BoolPtr { + return refreshable.NewBoolPtr(r.MapSecurityConfig(func(i httpclient.SecurityConfig) interface{} { + return i.InsecureSkipVerify + })) +} + type RefreshableStringToClientConfig interface { refreshable.Refreshable CurrentStringToClientConfig() map[string]httpclient.ClientConfig diff --git a/go.mod b/go.mod index 2e3f3403..f638fcaf 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/palantir/witchcraft-go-server/v2 -go 1.21 +go 1.22.0 + +toolchain go1.22.7 require ( github.com/gorilla/mux v1.7.3 github.com/julienschmidt/httprouter v1.3.0 github.com/nmiyake/pkg/dirs v1.0.0 - github.com/palantir/conjure-go-runtime/v2 v2.79.0 - github.com/palantir/go-encrypted-config-value v1.36.0 + github.com/palantir/conjure-go-runtime/v2 v2.82.0 + github.com/palantir/go-encrypted-config-value v1.37.0 github.com/palantir/go-metrics v1.1.1 github.com/palantir/pkg/httpserver v1.1.0 github.com/palantir/pkg/metrics v1.7.0 diff --git a/go.sum b/go.sum index 52de91cf..d5bc2149 100644 --- a/go.sum +++ b/go.sum @@ -48,10 +48,10 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/palantir/conjure-go-runtime/v2 v2.79.0 h1:TwQkhFMANIFpnX24q91XhxiNrAewmArdfpyeS6CUi2Q= -github.com/palantir/conjure-go-runtime/v2 v2.79.0/go.mod h1:+pd+AYgo5d3mdXX8Wn56pu/UK04DtEO5XX9lXqDvveY= -github.com/palantir/go-encrypted-config-value v1.36.0 h1:HWpyr8YkczJfkadN8dC+jqe9EyGbxieXoXJEF1g2vJM= -github.com/palantir/go-encrypted-config-value v1.36.0/go.mod h1:O+MA4qjmCndRlXNlZ7YI8HKVuyDQo54Wnm90oyqUzuY= +github.com/palantir/conjure-go-runtime/v2 v2.82.0 h1:wEyMBSE/CaoUiiGmRg/vNLgagWn3qaMh77LBG1Ceiik= +github.com/palantir/conjure-go-runtime/v2 v2.82.0/go.mod h1:SgF6EMcUTPrZ6xJ4D2Zvu7D1FxLzysuwUu+Y75grBnM= +github.com/palantir/go-encrypted-config-value v1.37.0 h1:Hwn3ouH0srIIrX0oXBX6AOHDHNOZUw3mbJR/Wg7RI94= +github.com/palantir/go-encrypted-config-value v1.37.0/go.mod h1:OUNyHOakyq62heTwvv0ID28fzCYkR67dduHIDByCmqs= github.com/palantir/go-metrics v1.1.1 h1:YL/UmptBjrC6iSCTVr7vfuIcjL0M359Da3/gBGNny10= github.com/palantir/go-metrics v1.1.1/go.mod h1:fRkuipBnsI4nD8Vd9UNcrUJvD8Y0wOJMSbicygcBrGs= github.com/palantir/pkg v1.1.0 h1:0EhrSUP8oeeh3MUvk7V/UU7WmsN1UiJNTvNj0sN9Cpo= diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/authn.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/authn.go index 29ff80fe..c82edf52 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/authn.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/authn.go @@ -44,21 +44,20 @@ func (h *authTokenMiddleware) RoundTrip(req *http.Request, next http.RoundTrippe if err != nil { return nil, err } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + if token != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } return next.RoundTrip(req) } func newAuthTokenMiddlewareFromRefreshable(token refreshable.StringPtr) Middleware { - return &conditionalMiddleware{ - Disabled: refreshable.NewBool(token.MapStringPtr(func(s *string) interface{} { - return s == nil - })), - Delegate: &authTokenMiddleware{provideToken: func(ctx context.Context) (string, error) { + return &authTokenMiddleware{ + provideToken: func(ctx context.Context) (string, error) { if s := token.CurrentStringPtr(); s != nil { return *s, nil } return "", nil - }}, + }, } } @@ -69,32 +68,22 @@ func newAuthTokenMiddlewareFromRefreshable(token refreshable.StringPtr) Middlewa // (2) an empty BasicAuth and a non-nil error. type BasicAuthProvider func(context.Context) (BasicAuth, error) -// basicAuthMiddleware wraps a refreshing BasicAuth pointer and injects basic auth credentials if the pointer is not nil -type basicAuthMiddleware struct { - provider BasicAuthProvider -} - -func (b *basicAuthMiddleware) RoundTrip(req *http.Request, next http.RoundTripper) (*http.Response, error) { - basicAuth, err := b.provider(req.Context()) - if err != nil { - return nil, err - } - setBasicAuth(req.Header, basicAuth.User, basicAuth.Password) - return next.RoundTrip(req) -} +// BasicAuthOptionalProvider accepts a context and returns either: +// +// (1) nil, nil to indicate that no BasicAuth should not be set on the request, or +// +// (2) a nonempty BasicAuth and a nil error, or +// +// (3) a nil BasicAuth and a non-nil error. +type BasicAuthOptionalProvider func(context.Context) (*BasicAuth, error) func newBasicAuthMiddlewareFromRefreshable(auth refreshingclient.RefreshableBasicAuthPtr) Middleware { - return &conditionalMiddleware{ - Disabled: refreshable.NewBool(auth.MapBasicAuthPtr(func(auth *refreshingclient.BasicAuth) interface{} { - return auth == nil - })), - Delegate: &basicAuthMiddleware{provider: func(ctx context.Context) (BasicAuth, error) { - if b := auth.CurrentBasicAuthPtr(); b != nil { - return BasicAuth{User: b.User, Password: b.Password}, nil - } - return BasicAuth{}, nil - }}, - } + return MiddlewareFunc(func(req *http.Request, next http.RoundTripper) (*http.Response, error) { + if basicAuth := auth.CurrentBasicAuthPtr(); basicAuth != nil { + setBasicAuth(req.Header, basicAuth.User, basicAuth.Password) + } + return next.RoundTrip(req) + }) } func setBasicAuth(h http.Header, username, password string) { diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go index 69df1d16..10deea2e 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client.go @@ -50,6 +50,7 @@ type Client interface { } type clientImpl struct { + serviceName refreshable.String client RefreshableHTTPClient middlewares []Middleware errorDecoderMiddleware Middleware @@ -84,7 +85,7 @@ func (c *clientImpl) Delete(ctx context.Context, params ...RequestParam) (*http. func (c *clientImpl) Do(ctx context.Context, params ...RequestParam) (*http.Response, error) { uris := c.uriScorer.CurrentURIScoringMiddleware().GetURIsInOrderOfIncreasingScore() if len(uris) == 0 { - return nil, werror.ErrorWithContextParams(ctx, "no base URIs are configured") + return nil, werror.WrapWithContextParams(ctx, ErrEmptyURIs, "", werror.SafeParam("serviceName", c.serviceName.CurrentString())) } attempts := 2 * len(uris) diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_builder.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_builder.go index a37f75ee..91db6014 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_builder.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_builder.go @@ -18,6 +18,7 @@ package httpclient import ( "context" "crypto/tls" + "fmt" "net/http" "time" @@ -28,7 +29,6 @@ import ( "github.com/palantir/pkg/refreshable" "github.com/palantir/pkg/tlsconfig" werror "github.com/palantir/witchcraft-go-error" - "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" ) const ( @@ -46,12 +46,23 @@ const ( defaultMaxBackoff = 2 * time.Second ) +var ( + // ErrEmptyURIs is returned when the client expects to have base URIs configured to make requests, but the URIs are empty. + // This check occurs in two places: when the client is constructed and when a request is executed. + // To avoid the construction validation, use WithAllowCreateWithEmptyURIs(). + ErrEmptyURIs = fmt.Errorf("httpclient URLs must not be empty") +) + type clientBuilder struct { HTTP *httpClientBuilder URIs refreshable.StringSlice URIScorerBuilder func([]string) internal.URIScoringMiddleware + // If false, NewClient() will return an error when URIs.Current() is empty. + // This allows for a refreshable URI slice to be populated after construction but before use. + AllowEmptyURIs bool + ErrorDecoder ErrorDecoder BytesBufferPool bytesbuffers.Pool @@ -60,10 +71,10 @@ type clientBuilder struct { } type httpClientBuilder struct { - ServiceNameTag metrics.Tag // Service name is not refreshable. + ServiceName refreshable.String Timeout refreshable.Duration DialerParams refreshingclient.RefreshableDialerParams - TLSConfig *tls.Config // TODO: Make this refreshing and wire into transport + TLSConfig *tls.Config // If unset, config in TransportParams will be used. TransportParams refreshingclient.RefreshableTransportParams Middlewares []Middleware @@ -72,9 +83,9 @@ type httpClientBuilder struct { // These middleware options are not refreshed anywhere because they are not in ClientConfig, // but they could be made refreshable if ever needed. - CreateRequestSpan bool - DisableRecovery bool - InjectTraceHeaders bool + DisableRequestSpan bool + DisableRecovery bool + DisableTraceHeaders bool } func (b *httpClientBuilder) Build(ctx context.Context, params ...HTTPClientParam) (RefreshableHTTPClient, error) { @@ -86,16 +97,22 @@ func (b *httpClientBuilder) Build(ctx context.Context, params ...HTTPClientParam return nil, err } } - transport := refreshingclient.NewRefreshableTransport(ctx, - b.TransportParams, - b.TLSConfig, - refreshingclient.NewRefreshableDialer(ctx, b.DialerParams)) - transport = wrapTransport(transport, newMetricsMiddleware(b.ServiceNameTag, b.MetricsTagProviders, b.DisableMetrics)) - transport = wrapTransport(transport, traceMiddleware{ - ServiceName: b.ServiceNameTag.Value(), - CreateRequestSpan: b.CreateRequestSpan, - InjectHeaders: b.InjectTraceHeaders, - }) + + var tlsProvider refreshingclient.TLSProvider + if b.TLSConfig != nil { + tlsProvider = refreshingclient.NewStaticTLSConfigProvider(b.TLSConfig) + } else { + refreshableProvider, err := refreshingclient.NewRefreshableTLSConfig(ctx, b.TransportParams.TLS()) + if err != nil { + return nil, err + } + tlsProvider = refreshableProvider + } + + dialer := refreshingclient.NewRefreshableDialer(ctx, b.DialerParams) + transport := refreshingclient.NewRefreshableTransport(ctx, b.TransportParams, tlsProvider, dialer) + transport = wrapTransport(transport, newMetricsMiddleware(b.ServiceName, b.MetricsTagProviders, b.DisableMetrics)) + transport = wrapTransport(transport, newTraceMiddleware(b.ServiceName, b.DisableRequestSpan, b.DisableTraceHeaders)) if !b.DisableRecovery { transport = wrapTransport(transport, recoveryMiddleware{}) } @@ -115,7 +132,7 @@ func NewClient(params ...ClientParam) (Client, error) { // We apply "sane defaults" before applying the provided params. func NewClientFromRefreshableConfig(ctx context.Context, config RefreshableClientConfig, params ...ClientParam) (Client, error) { b := newClientBuilder() - if err := newClientBuilderFromRefreshableConfig(ctx, config, b, nil, false); err != nil { + if err := newClientBuilderFromRefreshableConfig(ctx, config, b, nil); err != nil { return nil, err } return newClient(ctx, b, params...) @@ -131,7 +148,10 @@ func newClient(ctx context.Context, b *clientBuilder, params ...ClientParam) (Cl } } if b.URIs == nil { - return nil, werror.Error("httpclient URLs must not be empty", werror.SafeParam("serviceName", b.HTTP.ServiceNameTag.Value())) + return nil, werror.ErrorWithContextParams(ctx, "httpclient URLs must be set in configuration or by constructor param", werror.SafeParam("serviceName", b.HTTP.ServiceName.CurrentString())) + } + if !b.AllowEmptyURIs && len(b.URIs.CurrentStringSlice()) == 0 { + return nil, werror.WrapWithContextParams(ctx, ErrEmptyURIs, "", werror.SafeParam("serviceName", b.HTTP.ServiceName.CurrentString())) } var edm Middleware @@ -158,6 +178,7 @@ func newClient(ctx context.Context, b *clientBuilder, params ...ClientParam) (Cl return b.URIScorerBuilder(uris) }) return &clientImpl{ + serviceName: b.HTTP.ServiceName, client: httpClient, uriScorer: uriScorer, maxAttempts: b.MaxAttempts, @@ -187,7 +208,7 @@ type RefreshableHTTPClient = refreshingclient.RefreshableHTTPClient // We apply "sane defaults" before applying the provided params. func NewHTTPClientFromRefreshableConfig(ctx context.Context, config RefreshableClientConfig, params ...HTTPClientParam) (RefreshableHTTPClient, error) { b := newClientBuilder() - if err := newClientBuilderFromRefreshableConfig(ctx, config, b, nil, true); err != nil { + if err := newClientBuilderFromRefreshableConfig(ctx, config, b, nil); err != nil { return nil, err } return b.HTTP.Build(ctx, params...) @@ -197,8 +218,8 @@ func newClientBuilder() *clientBuilder { defaultTLSConfig, _ := tlsconfig.NewClientConfig() return &clientBuilder{ HTTP: &httpClientBuilder{ - ServiceNameTag: metrics.Tag{}, - Timeout: refreshable.NewDuration(refreshable.NewDefaultRefreshable(defaultHTTPTimeout)), + ServiceName: refreshable.NewString(refreshable.NewDefaultRefreshable("")), + Timeout: refreshable.NewDuration(refreshable.NewDefaultRefreshable(defaultHTTPTimeout)), DialerParams: refreshingclient.NewRefreshingDialerParams(refreshable.NewDefaultRefreshable(refreshingclient.DialerParams{ DialTimeout: defaultDialTimeout, KeepAlive: defaultKeepAlive, @@ -219,12 +240,12 @@ func newClientBuilder() *clientBuilder { HTTP2ReadIdleTimeout: defaultHTTP2ReadIdleTimeout, HTTP2PingTimeout: defaultHTTP2PingTimeout, })), + Middlewares: nil, DisableMetrics: refreshable.NewBool(refreshable.NewDefaultRefreshable(false)), - DisableRecovery: false, - CreateRequestSpan: true, - InjectTraceHeaders: true, MetricsTagProviders: nil, - Middlewares: nil, + DisableRecovery: false, + DisableRequestSpan: false, + DisableTraceHeaders: false, }, URIs: nil, BytesBufferPool: nil, @@ -237,41 +258,28 @@ func newClientBuilder() *clientBuilder { } } -func newClientBuilderFromRefreshableConfig(ctx context.Context, config RefreshableClientConfig, b *clientBuilder, reloadErrorSubmitter func(error), isHTTPClient bool) error { - var err error - b.HTTP.ServiceNameTag, err = metrics.NewTag(MetricTagServiceName, config.CurrentClientConfig().ServiceName) - if err != nil { - return werror.WrapWithContextParams(ctx, err, "invalid service name metrics tag") - } - config.ServiceName().SubscribeToString(func(s string) { - svc1log.FromContext(ctx).Warn("conjure-go-runtime: Service name changed but can not be live-reloaded.", - svc1log.SafeParam("existingServiceName", b.HTTP.ServiceNameTag.Value()), - svc1log.SafeParam("updatedServiceName", s)) - }) - - if tlsConfig, err := subscribeTLSConfigUpdateWarning(ctx, config.Security()); err != nil { - return err - } else if tlsConfig != nil { - b.HTTP.TLSConfig = tlsConfig - } - +func newClientBuilderFromRefreshableConfig(ctx context.Context, config RefreshableClientConfig, b *clientBuilder, reloadErrorSubmitter func(error)) error { refreshingParams, err := refreshable.NewMapValidatingRefreshable(config, func(i interface{}) (interface{}, error) { - p, err := newValidatedClientParamsFromConfig(ctx, i.(ClientConfig), isHTTPClient) + p, err := newValidatedClientParamsFromConfig(ctx, i.(ClientConfig)) if reloadErrorSubmitter != nil { reloadErrorSubmitter(err) } return p, err }) - validParams := refreshingclient.NewRefreshingValidatedClientParams(refreshingParams) if err != nil { return err } + validParams := refreshingclient.NewRefreshingValidatedClientParams(refreshingParams) + b.HTTP.ServiceName = validParams.ServiceName() b.HTTP.DialerParams = validParams.Dialer() b.HTTP.TransportParams = validParams.Transport() b.HTTP.Timeout = validParams.Timeout() b.HTTP.DisableMetrics = validParams.DisableMetrics() - b.HTTP.MetricsTagProviders = append(b.HTTP.MetricsTagProviders, refreshableMetricsTagsProvider{validParams.MetricsTags()}) + b.HTTP.MetricsTagProviders = append(b.HTTP.MetricsTagProviders, + TagsProviderFunc(func(*http.Request, *http.Response, error) metrics.Tags { + return validParams.CurrentValidatedClientParams().MetricsTags + })) b.HTTP.Middlewares = append(b.HTTP.Middlewares, newAuthTokenMiddlewareFromRefreshable(validParams.APIToken()), newBasicAuthMiddlewareFromRefreshable(validParams.BasicAuth())) diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_params.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_params.go index 54c10194..ef3bbeb9 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_params.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/client_params.go @@ -24,7 +24,6 @@ import ( "github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal" "github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient" "github.com/palantir/pkg/bytesbuffers" - "github.com/palantir/pkg/metrics" "github.com/palantir/pkg/refreshable" werror "github.com/palantir/witchcraft-go-error" ) @@ -108,11 +107,7 @@ func WithConfigForHTTPClient(c ClientConfig) HTTPClientParam { func WithServiceName(serviceName string) ClientOrHTTPClientParam { return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error { - tag, err := metrics.NewTag(MetricTagServiceName, serviceName) - if err != nil { - return err - } - b.ServiceNameTag = tag + b.ServiceName = refreshable.NewString(refreshable.NewDefaultRefreshable(serviceName)) return nil }) } @@ -205,7 +200,7 @@ func WithDisablePanicRecovery() ClientOrHTTPClientParam { // 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.CreateRequestSpan = false + b.DisableRequestSpan = true return nil }) } @@ -215,7 +210,7 @@ func WithDisableTracing() ClientOrHTTPClientParam { // then the client will attach this traceId as a header for future services to do the same if desired func WithDisableTraceHeaderPropagation() ClientOrHTTPClientParam { return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error { - b.InjectTraceHeaders = false + b.DisableTraceHeaders = true return nil }) } @@ -367,11 +362,16 @@ func WithTLSConfig(conf *tls.Config) ClientOrHTTPClientParam { // WithTLSInsecureSkipVerify sets the InsecureSkipVerify field for the HTTP client's tls config. // This option should only be used in clients that have way to establish trust with servers. +// If WithTLSConfig is used, the config's InsecureSkipVerify is set to true. func WithTLSInsecureSkipVerify() ClientOrHTTPClientParam { return clientOrHTTPClientParamFunc(func(b *httpClientBuilder) error { if b.TLSConfig != nil { b.TLSConfig.InsecureSkipVerify = true } + b.TransportParams = refreshingclient.ConfigureTransport(b.TransportParams, func(p refreshingclient.TransportParams) refreshingclient.TransportParams { + p.TLS.InsecureSkipVerify = true + return p + }) return nil }) } @@ -466,6 +466,16 @@ func WithRefreshableBaseURLs(urls refreshable.StringSlice) ClientParam { }) } +// WithAllowCreateWithEmptyURIs prevents NewClient from returning an error when the URI slice is empty. +// This is useful when the URIs are not known at client creation time but will be populated by a refreshable. +// Requests will error if attempted before URIs are populated. +func WithAllowCreateWithEmptyURIs() ClientParam { + return clientParamFunc(func(b *clientBuilder) error { + b.AllowEmptyURIs = true + return nil + }) +} + // WithMaxBackoff sets the maximum backoff between retried calls to the same URI. // Defaults to 2 seconds. <= 0 indicates no limit. func WithMaxBackoff(maxBackoff time.Duration) ClientParam { @@ -542,13 +552,38 @@ func WithErrorDecoder(errorDecoder ErrorDecoder) ClientParam { // WithBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and // password. func WithBasicAuth(user, password string) ClientOrHTTPClientParam { - return WithMiddleware(&basicAuthMiddleware{provider: func(ctx context.Context) (BasicAuth, error) { + return WithBasicAuthProvider(func(context.Context) (BasicAuth, error) { return BasicAuth{User: user, Password: password}, nil - }}) + }) } +// WithBasicAuthProvider sets the request's Authorization header to use HTTP Basic Authentication. +// The provider is expected to always return a nonempty BasicAuth value, or an error. func WithBasicAuthProvider(provider BasicAuthProvider) ClientOrHTTPClientParam { - return WithMiddleware(&basicAuthMiddleware{provider: provider}) + return WithBasicAuthOptionalProvider(func(ctx context.Context) (*BasicAuth, error) { + basicAuth, err := provider(ctx) + if err != nil { + return nil, err + } + return &basicAuth, nil + }) +} + +// WithBasicAuthOptionalProvider sets the request's Authorization header to use HTTP Basic Authentication based on the +// return value of the provided BasicAuthOptionalProvider. If the provider returns a non-nil error, if the returned +// BasicAuth value is non-nil then its values are set on the header, while if the returned BasicAuth value is nil then +// no basic authentication header values are set. +func WithBasicAuthOptionalProvider(provider BasicAuthOptionalProvider) ClientOrHTTPClientParam { + return WithMiddleware(MiddlewareFunc(func(req *http.Request, next http.RoundTripper) (*http.Response, error) { + basicAuth, err := provider(req.Context()) + if err != nil { + return nil, err + } + if basicAuth != nil { + setBasicAuth(req.Header, basicAuth.User, basicAuth.Password) + } + return next.RoundTrip(req) + })) } // WithBalancedURIScoring adds middleware that prioritizes sending requests to URIs with the fewest in-flight requests diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/config.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/config.go index 99face43..5807b0a1 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/config.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/config.go @@ -17,17 +17,14 @@ package httpclient import ( "bytes" "context" - "crypto/tls" "io/ioutil" "net/url" - "sort" + "slices" "time" "github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient" "github.com/palantir/pkg/metrics" - "github.com/palantir/pkg/tlsconfig" werror "github.com/palantir/witchcraft-go-error" - "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" ) // ServicesConfig is the top-level configuration struct for all HTTP clients. It supports @@ -88,6 +85,12 @@ type ClientConfig struct { // IdleConnTimeout 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. ExpectContinueTimeout *time.Duration `json:"expect-continue-timeout,omitempty" yaml:"expect-continue-timeout,omitempty"` + // ResponseHeaderTimeout, if non-zero, 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. + ResponseHeaderTimeout *time.Duration `json:"response-header-timeout,omitempty" yaml:"response-header-timeout,omitempty"` + // KeepAlive sets the time to keep idle connections alive. + // If unset, the client defaults to 30s. If set to 0, the client will not keep connections alive. + KeepAlive *time.Duration `json:"keep-alive,omitempty" yaml:"keep-alive,omitempty"` // HTTP2ReadIdleTimeout sets the maximum time to wait before sending periodic health checks (pings) for an HTTP/2 connection. // If unset, the client defaults to 30s for HTTP/2 clients. @@ -130,6 +133,10 @@ type SecurityConfig struct { CAFiles []string `json:"ca-files,omitempty" yaml:"ca-files,omitempty"` CertFile string `json:"cert-file,omitempty" yaml:"cert-file,omitempty"` KeyFile string `json:"key-file,omitempty" yaml:"key-file,omitempty"` + + // InsecureSkipVerify sets the InsecureSkipVerify field for the HTTP client's tls config. + // This option should only be used in clients that have other ways to establish trust with servers. + InsecureSkipVerify *bool `json:"insecure-skip-verify,omitempty" yaml:"insecure-skip-verify,omitempty"` } // MustClientConfig returns an error if the service name is not configured. @@ -188,6 +195,12 @@ func MergeClientConfig(conf, defaults ClientConfig) ClientConfig { if conf.ExpectContinueTimeout == nil { conf.ExpectContinueTimeout = defaults.ExpectContinueTimeout } + if conf.ResponseHeaderTimeout == nil { + conf.ResponseHeaderTimeout = defaults.ResponseHeaderTimeout + } + if conf.KeepAlive == nil { + conf.KeepAlive = defaults.KeepAlive + } if conf.HTTP2ReadIdleTimeout == nil { conf.HTTP2ReadIdleTimeout = defaults.HTTP2ReadIdleTimeout } @@ -238,6 +251,9 @@ func MergeClientConfig(conf, defaults ClientConfig) ClientConfig { if conf.Security.KeyFile == "" { conf.Security.KeyFile = defaults.Security.KeyFile } + if conf.Security.InsecureSkipVerify == nil { + conf.Security.InsecureSkipVerify = defaults.Security.InsecureSkipVerify + } return conf } @@ -320,6 +336,12 @@ func configToParams(c ClientConfig) ([]ClientParam, error) { if c.ExpectContinueTimeout != nil && *c.ExpectContinueTimeout != 0 { params = append(params, WithExpectContinueTimeout(*c.ExpectContinueTimeout)) } + if c.ResponseHeaderTimeout != nil && *c.ResponseHeaderTimeout != 0 { + params = append(params, WithResponseHeaderTimeout(*c.ResponseHeaderTimeout)) + } + if c.KeepAlive != nil && *c.KeepAlive != 0 { + params = append(params, WithKeepAlive(*c.KeepAlive)) + } if c.HTTP2ReadIdleTimeout != nil && *c.HTTP2ReadIdleTimeout >= 0 { params = append(params, WithHTTP2ReadIdleTimeout(*c.HTTP2ReadIdleTimeout)) } @@ -337,19 +359,18 @@ func configToParams(c ClientConfig) ([]ClientParam, error) { } // N.B. we only have one timeout field (not based on method) so just take the max of read and write for now. - var timeout time.Duration - if orZero(c.WriteTimeout) > orZero(c.ReadTimeout) { - timeout = *c.WriteTimeout - } else if c.ReadTimeout != nil { - timeout = *c.ReadTimeout - } + timeout := max(derefPtr(c.WriteTimeout, 0), derefPtr(c.ReadTimeout, 0)) if timeout != 0 { params = append(params, WithHTTPTimeout(timeout)) } // Security (TLS) Config - if tlsConfig, err := newTLSConfig(c.Security); err != nil { + if tlsConfig, err := refreshingclient.NewTLSConfig(context.TODO(), refreshingclient.TLSParams{ + CAFiles: c.Security.CAFiles, + CertFile: c.Security.CertFile, + KeyFile: c.Security.KeyFile, + }); err != nil { return nil, err } else if tlsConfig != nil { params = append(params, WithTLSConfig(tlsConfig)) @@ -364,22 +385,23 @@ func RefreshableClientConfigFromServiceConfig(servicesConfig RefreshableServices })) } -func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig, isHTTPClient bool) (refreshingclient.ValidatedClientParams, error) { +func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig) (refreshingclient.ValidatedClientParams, error) { dialer := refreshingclient.DialerParams{ - DialTimeout: derefDurationPtr(config.ConnectTimeout, defaultDialTimeout), - KeepAlive: defaultKeepAlive, + DialTimeout: derefPtr(config.ConnectTimeout, defaultDialTimeout), + KeepAlive: derefPtr(config.KeepAlive, defaultKeepAlive), } transport := refreshingclient.TransportParams{ - MaxIdleConns: derefIntPtr(config.MaxIdleConns, defaultMaxIdleConns), - MaxIdleConnsPerHost: derefIntPtr(config.MaxIdleConnsPerHost, defaultMaxIdleConnsPerHost), - DisableHTTP2: derefBoolPtr(config.DisableHTTP2, false), - IdleConnTimeout: derefDurationPtr(config.IdleConnTimeout, defaultIdleConnTimeout), - ExpectContinueTimeout: derefDurationPtr(config.ExpectContinueTimeout, defaultExpectContinueTimeout), - HTTP2PingTimeout: derefDurationPtr(config.HTTP2PingTimeout, defaultHTTP2PingTimeout), - HTTP2ReadIdleTimeout: derefDurationPtr(config.HTTP2ReadIdleTimeout, defaultHTTP2ReadIdleTimeout), - ProxyFromEnvironment: derefBoolPtr(config.ProxyFromEnvironment, true), - TLSHandshakeTimeout: derefDurationPtr(config.TLSHandshakeTimeout, defaultTLSHandshakeTimeout), + MaxIdleConns: derefPtr(config.MaxIdleConns, defaultMaxIdleConns), + MaxIdleConnsPerHost: derefPtr(config.MaxIdleConnsPerHost, defaultMaxIdleConnsPerHost), + DisableHTTP2: derefPtr(config.DisableHTTP2, false), + IdleConnTimeout: derefPtr(config.IdleConnTimeout, defaultIdleConnTimeout), + ExpectContinueTimeout: derefPtr(config.ExpectContinueTimeout, defaultExpectContinueTimeout), + ResponseHeaderTimeout: derefPtr(config.ResponseHeaderTimeout, 0), + HTTP2PingTimeout: derefPtr(config.HTTP2PingTimeout, defaultHTTP2PingTimeout), + HTTP2ReadIdleTimeout: derefPtr(config.HTTP2ReadIdleTimeout, defaultHTTP2ReadIdleTimeout), + ProxyFromEnvironment: derefPtr(config.ProxyFromEnvironment, true), + TLSHandshakeTimeout: derefPtr(config.TLSHandshakeTimeout, defaultTLSHandshakeTimeout), } if config.ProxyURL != nil { @@ -424,8 +446,8 @@ func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig } retryParams := refreshingclient.RetryParams{ - InitialBackoff: derefDurationPtr(config.InitialBackoff, defaultInitialBackoff), - MaxBackoff: derefDurationPtr(config.MaxBackoff, defaultMaxBackoff), + InitialBackoff: derefPtr(config.InitialBackoff, defaultInitialBackoff), + MaxBackoff: derefPtr(config.MaxBackoff, defaultMaxBackoff), } var maxAttempts *int if config.MaxNumRetries != nil { @@ -435,8 +457,8 @@ func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig timeout := defaultHTTPTimeout if config.ReadTimeout != nil || config.WriteTimeout != nil { - rt := derefDurationPtr(config.ReadTimeout, 0) - wt := derefDurationPtr(config.WriteTimeout, 0) + rt := derefPtr(config.ReadTimeout, 0) + wt := derefPtr(config.WriteTimeout, 0) // return max of read and write if rt > wt { timeout = rt @@ -455,11 +477,7 @@ func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig } uris = append(uris, uriStr) } - // Plain HTTP clients do not store URIs - if !isHTTPClient && len(uris) == 0 { - return refreshingclient.ValidatedClientParams{}, werror.ErrorWithContextParams(ctx, "httpclient URLs must not be empty") - } - sort.Strings(uris) + slices.Sort(uris) return refreshingclient.ValidatedClientParams{ APIToken: apiToken, @@ -469,75 +487,16 @@ func newValidatedClientParamsFromConfig(ctx context.Context, config ClientConfig MaxAttempts: maxAttempts, MetricsTags: metricsTags, Retry: retryParams, + ServiceName: config.ServiceName, Timeout: timeout, Transport: transport, URIs: uris, }, nil } -func subscribeTLSConfigUpdateWarning(ctx context.Context, security RefreshableSecurityConfig) (*tls.Config, error) { - //TODO: Implement refreshable TLS configuration. - // It is hard to represent all of the configuration (e.g. a dynamic function for GetCertificate) in primitive values friendly to reflect.DeepEqual. - currentSecurity := security.CurrentSecurityConfig() - - security.CAFiles().SubscribeToStringSlice(func(caFiles []string) { - svc1log.FromContext(ctx).Warn("conjure-go-runtime: CAFiles configuration changed but can not be live-reloaded.", - svc1log.SafeParam("existingCAFiles", currentSecurity.CAFiles), - svc1log.SafeParam("ignoredCAFiles", caFiles)) - }) - security.CertFile().SubscribeToString(func(certFile string) { - svc1log.FromContext(ctx).Warn("conjure-go-runtime: CertFile configuration changed but can not be live-reloaded.", - svc1log.SafeParam("existingCertFile", currentSecurity.CertFile), - svc1log.SafeParam("ignoredCertFile", certFile)) - }) - security.KeyFile().SubscribeToString(func(keyFile string) { - svc1log.FromContext(ctx).Warn("conjure-go-runtime: KeyFile configuration changed but can not be live-reloaded.", - svc1log.SafeParam("existingKeyFile", currentSecurity.KeyFile), - svc1log.SafeParam("ignoredKeyFile", keyFile)) - }) - - return newTLSConfig(currentSecurity) -} - -func newTLSConfig(security SecurityConfig) (*tls.Config, error) { - var tlsParams []tlsconfig.ClientParam - if len(security.CAFiles) != 0 { - tlsParams = append(tlsParams, tlsconfig.ClientRootCAFiles(security.CAFiles...)) - } - if security.CertFile != "" && security.KeyFile != "" { - tlsParams = append(tlsParams, tlsconfig.ClientKeyPairFiles(security.CertFile, security.KeyFile)) - } - if len(tlsParams) != 0 { - tlsConfig, err := tlsconfig.NewClientConfig(tlsParams...) - if err != nil { - return nil, werror.Wrap(err, "failed to build tlsConfig") - } - return tlsConfig, nil - } - return nil, nil -} - -func derefDurationPtr(durPtr *time.Duration, defaultVal time.Duration) time.Duration { - if durPtr == nil { - return defaultVal - } - return *durPtr -} - -func derefIntPtr(intPtr *int, defaultVal int) int { - if intPtr == nil { +func derefPtr[T any](ptr *T, defaultVal T) T { + if ptr == nil { return defaultVal } - return *intPtr -} - -func derefBoolPtr(boolPtr *bool, defaultVal bool) bool { - if boolPtr == nil { - return defaultVal - } - return *boolPtr -} - -func orZero(d *time.Duration) time.Duration { - return derefDurationPtr(d, 0) + return *ptr } diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2.go deleted file mode 100644 index f3810a68..00000000 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2021 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. - -//go:build !go1.16 -// +build !go1.16 - -package refreshingclient - -import ( - "net/http" - "time" - - werror "github.com/palantir/witchcraft-go-error" - "golang.org/x/net/http2" -) - -// configureHTTP2 will attempt to configure net/http HTTP/1 Transport to use HTTP/2. -// It returns an error if t1 has already been HTTP/2-enabled. -func configureHTTP2(t1 *http.Transport, _, _ time.Duration) error { - if err := http2.ConfigureTransport(t1); err != nil { - return werror.Wrap(err, "failed to configure transport for http2") - } - return nil -} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2_go16.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2_go16.go deleted file mode 100644 index 4d9d4663..00000000 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/configure_http2_go16.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2021 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. - -//go:build go1.16 -// +build go1.16 - -package refreshingclient - -import ( - "net/http" - "time" - - werror "github.com/palantir/witchcraft-go-error" - "golang.org/x/net/http2" -) - -// configureHTTP2 will attempt to configure net/http HTTP/1 Transport to use HTTP/2. -// The provided readIdleTimeout will set the underlying HTTP/2 transport ReadIdleTimeout. -// It returns an error if t1 has already been HTTP/2-enabled. -func configureHTTP2(t1 *http.Transport, readIdleTimeout, pingTimeout time.Duration) error { - http2Transport, err := http2.ConfigureTransports(t1) - if err != nil { - return werror.Wrap(err, "failed to configure transport for http2") - } - // ReadIdleTimeout is the amount of time to wait before running periodic health checks (pings) - // after not receiving a frame from the HTTP/2 connection. - // Setting this value will enable the health checks and allows broken idle - // connections to be pruned more quickly, preventing the client from - // attempting to re-use connections that will no longer work. - // ref: https://github.com/golang/go/issues/36026 - http2Transport.ReadIdleTimeout = readIdleTimeout - - // PingTimeout configures the amount of time to wait for a ping response (health check) - // before closing an HTTP/2 connection. The PingTimeout is only valid if - // the above ReadIdleTimeout is > 0. - http2Transport.PingTimeout = pingTimeout - - return nil -} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/tlsconfig.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/tlsconfig.go new file mode 100644 index 00000000..51933a17 --- /dev/null +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/tlsconfig.go @@ -0,0 +1,99 @@ +// Copyright (c) 2024 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 refreshingclient + +import ( + "context" + "crypto/tls" + + "github.com/palantir/pkg/refreshable" + "github.com/palantir/pkg/tlsconfig" + werror "github.com/palantir/witchcraft-go-error" + "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" +) + +// TLSParams contains the parameters needed to build a *tls.Config. +// Its fields must all be compatible with reflect.DeepEqual. +type TLSParams struct { + CAFiles []string + CertFile string + KeyFile string + InsecureSkipVerify bool +} + +type TLSProvider interface { + GetTLSConfig(ctx context.Context) *tls.Config +} + +// StaticTLSConfigProvider is a TLSProvider that always returns the same *tls.Config. +type StaticTLSConfigProvider tls.Config + +func NewStaticTLSConfigProvider(tlsConfig *tls.Config) *StaticTLSConfigProvider { + return (*StaticTLSConfigProvider)(tlsConfig) +} + +func (p *StaticTLSConfigProvider) GetTLSConfig(context.Context) *tls.Config { + return (*tls.Config)(p) +} + +type RefreshableTLSConfig struct { + r *refreshable.ValidatingRefreshable // contains *tls.Config +} + +// NewRefreshableTLSConfig evaluates the provided TLSParams and returns a RefreshableTLSConfig that will update the +// underlying *tls.Config when the TLSParams change. +// IF the initial TLSParams are invalid, NewRefreshableTLSConfig will return an error. +// If the updated TLSParams are invalid, the RefreshableTLSConfig will continue to use the previous value and log the error. +// +// N.B. This subscription only fires when the paths are updated, not when the contents of the files are updated. +// We could consider adding a file refreshable to watch the key and cert files. +func NewRefreshableTLSConfig(ctx context.Context, params RefreshableTLSParams) (TLSProvider, error) { + r, err := refreshable.NewMapValidatingRefreshable(params, func(i interface{}) (interface{}, error) { + return NewTLSConfig(ctx, i.(TLSParams)) + }) + if err != nil { + return nil, werror.WrapWithContextParams(ctx, err, "failed to build RefreshableTLSConfig") + } + return RefreshableTLSConfig{r: r}, nil +} + +// GetTLSConfig returns the the most recent valid *tls.Config. +// If the last refreshable update resulted in an error, that error is logged and +// the previous value is returned. +func (r RefreshableTLSConfig) GetTLSConfig(ctx context.Context) *tls.Config { + if err := r.r.LastValidateErr(); err != nil { + svc1log.FromContext(ctx).Warn("Invalid TLS config. Using previous value.", svc1log.Stacktrace(err)) + } + return r.r.Current().(*tls.Config) +} + +// NewTLSConfig returns a *tls.Config built from the provided TLSParams. +func NewTLSConfig(ctx context.Context, p TLSParams) (*tls.Config, error) { + var tlsParams []tlsconfig.ClientParam + if len(p.CAFiles) != 0 { + tlsParams = append(tlsParams, tlsconfig.ClientRootCAFiles(p.CAFiles...)) + } + if p.CertFile != "" && p.KeyFile != "" { + tlsParams = append(tlsParams, tlsconfig.ClientKeyPairFiles(p.CertFile, p.KeyFile)) + } + if p.InsecureSkipVerify { + tlsParams = append(tlsParams, tlsconfig.ClientInsecureSkipVerify()) + } + tlsConfig, err := tlsconfig.NewClientConfig(tlsParams...) + if err != nil { + return nil, werror.WrapWithContextParams(ctx, err, "failed to build tlsConfig") + } + return tlsConfig, nil +} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/transport.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/transport.go index 68e1c39b..dc7c9b39 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/transport.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/transport.go @@ -16,13 +16,13 @@ package refreshingclient import ( "context" - "crypto/tls" "net/http" "net/url" "time" "github.com/palantir/pkg/refreshable" "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" + "golang.org/x/net/http2" ) type TransportParams struct { @@ -38,12 +38,14 @@ type TransportParams struct { ProxyFromEnvironment bool HTTP2ReadIdleTimeout time.Duration HTTP2PingTimeout time.Duration + + TLS TLSParams } -func NewRefreshableTransport(ctx context.Context, p RefreshableTransportParams, tlsConfig *tls.Config, dialer ContextDialer) http.RoundTripper { +func NewRefreshableTransport(ctx context.Context, p RefreshableTransportParams, tlsProvider TLSProvider, dialer ContextDialer) http.RoundTripper { return &RefreshableTransport{ Refreshable: p.MapTransportParams(func(p TransportParams) interface{} { - return newTransport(ctx, p, tlsConfig, dialer) + return newTransport(ctx, p, tlsProvider, dialer) }), } } @@ -62,13 +64,23 @@ type RefreshableTransport struct { refreshable.Refreshable // contains *http.Transport } -func (r RefreshableTransport) RoundTrip(req *http.Request) (*http.Response, error) { +func (r *RefreshableTransport) RoundTrip(req *http.Request) (*http.Response, error) { return r.Current().(*http.Transport).RoundTrip(req) } -func newTransport(ctx context.Context, p TransportParams, tlsConfig *tls.Config, dialer ContextDialer) *http.Transport { +func newTransport(ctx context.Context, p TransportParams, tlsProvider TLSProvider, dialer ContextDialer) *http.Transport { svc1log.FromContext(ctx).Debug("Reconstructing HTTP Transport") + + var transportProxy func(*http.Request) (*url.URL, error) + if p.HTTPProxyURL != nil { + transportProxy = func(*http.Request) (*url.URL, error) { return p.HTTPProxyURL, nil } + } else if p.ProxyFromEnvironment { + transportProxy = http.ProxyFromEnvironment + } + + tlsConfig := tlsProvider.GetTLSConfig(ctx) transport := &http.Transport{ + Proxy: transportProxy, DialContext: dialer.DialContext, MaxIdleConns: p.MaxIdleConns, MaxIdleConnsPerHost: p.MaxIdleConnsPerHost, @@ -80,19 +92,29 @@ func newTransport(ctx context.Context, p TransportParams, tlsConfig *tls.Config, ResponseHeaderTimeout: p.ResponseHeaderTimeout, } - if p.HTTPProxyURL != nil { - transport.Proxy = func(*http.Request) (*url.URL, error) { return p.HTTPProxyURL, nil } - } else if p.ProxyFromEnvironment { - transport.Proxy = http.ProxyFromEnvironment - } - if !p.DisableHTTP2 { - if err := configureHTTP2(transport, p.HTTP2ReadIdleTimeout, p.HTTP2PingTimeout); err != nil { + // Attempt to configure net/http HTTP/1 Transport to use HTTP/2. + http2Transport, err := http2.ConfigureTransports(transport) + if err != nil { // ConfigureTransport's only error as of this writing is the idempotent "protocol https already registered." // It should never happen in our usage because this is immediately after creation. // In case of something unexpected, log it and move on. svc1log.FromContext(ctx).Error("failed to configure transport for http2", svc1log.Stacktrace(err)) + } else { + // ReadIdleTimeout is the amount of time to wait before running periodic health checks (pings) + // after not receiving a frame from the HTTP/2 connection. + // Setting this value will enable the health checks and allows broken idle + // connections to be pruned more quickly, preventing the client from + // attempting to re-use connections that will no longer work. + // ref: https://github.com/golang/go/issues/36026 + http2Transport.ReadIdleTimeout = p.HTTP2ReadIdleTimeout + + // PingTimeout configures the amount of time to wait for a ping response (health check) + // before closing an HTTP/2 connection. The PingTimeout is only valid if + // the above ReadIdleTimeout is > 0. + http2Transport.PingTimeout = p.HTTP2PingTimeout } } + return transport } diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/types.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/types.go index 6ca91f03..d24c5f46 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/types.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/types.go @@ -32,6 +32,7 @@ type ValidatedClientParams struct { MaxAttempts *int MetricsTags metrics.Tags Retry RetryParams + ServiceName string Timeout time.Duration Transport TransportParams URIs []string diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/zz_generated_refreshables.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/zz_generated_refreshables.go index 4ff331e9..604a09ac 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/zz_generated_refreshables.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient/zz_generated_refreshables.go @@ -20,6 +20,7 @@ type RefreshableValidatedClientParams interface { MaxAttempts() refreshable.IntPtr MetricsTags() RefreshableTags Retry() RefreshableRetryParams + ServiceName() refreshable.String Timeout() refreshable.Duration Transport() RefreshableTransportParams URIs() refreshable.StringSlice @@ -91,6 +92,12 @@ func (r RefreshingValidatedClientParams) Retry() RefreshableRetryParams { })) } +func (r RefreshingValidatedClientParams) ServiceName() refreshable.String { + return refreshable.NewString(r.MapValidatedClientParams(func(i ValidatedClientParams) interface{} { + return i.ServiceName + })) +} + func (r RefreshingValidatedClientParams) Timeout() refreshable.Duration { return refreshable.NewDuration(r.MapValidatedClientParams(func(i ValidatedClientParams) interface{} { return i.Timeout @@ -403,6 +410,7 @@ type RefreshableTransportParams interface { ProxyFromEnvironment() refreshable.Bool HTTP2ReadIdleTimeout() refreshable.Duration HTTP2PingTimeout() refreshable.Duration + TLS() RefreshableTLSParams } type RefreshingTransportParams struct { @@ -494,3 +502,69 @@ func (r RefreshingTransportParams) HTTP2PingTimeout() refreshable.Duration { return i.HTTP2PingTimeout })) } + +func (r RefreshingTransportParams) TLS() RefreshableTLSParams { + return NewRefreshingTLSParams(r.MapTransportParams(func(i TransportParams) interface{} { + return i.TLS + })) +} + +type RefreshableTLSParams interface { + refreshable.Refreshable + CurrentTLSParams() TLSParams + MapTLSParams(func(TLSParams) interface{}) refreshable.Refreshable + SubscribeToTLSParams(func(TLSParams)) (unsubscribe func()) + + CAFiles() refreshable.StringSlice + CertFile() refreshable.String + KeyFile() refreshable.String + InsecureSkipVerify() refreshable.Bool +} + +type RefreshingTLSParams struct { + refreshable.Refreshable +} + +func NewRefreshingTLSParams(in refreshable.Refreshable) RefreshingTLSParams { + return RefreshingTLSParams{Refreshable: in} +} + +func (r RefreshingTLSParams) CurrentTLSParams() TLSParams { + return r.Current().(TLSParams) +} + +func (r RefreshingTLSParams) MapTLSParams(mapFn func(TLSParams) interface{}) refreshable.Refreshable { + return r.Map(func(i interface{}) interface{} { + return mapFn(i.(TLSParams)) + }) +} + +func (r RefreshingTLSParams) SubscribeToTLSParams(consumer func(TLSParams)) (unsubscribe func()) { + return r.Subscribe(func(i interface{}) { + consumer(i.(TLSParams)) + }) +} + +func (r RefreshingTLSParams) CAFiles() refreshable.StringSlice { + return refreshable.NewStringSlice(r.MapTLSParams(func(i TLSParams) interface{} { + return i.CAFiles + })) +} + +func (r RefreshingTLSParams) CertFile() refreshable.String { + return refreshable.NewString(r.MapTLSParams(func(i TLSParams) interface{} { + return i.CertFile + })) +} + +func (r RefreshingTLSParams) KeyFile() refreshable.String { + return refreshable.NewString(r.MapTLSParams(func(i TLSParams) interface{} { + return i.KeyFile + })) +} + +func (r RefreshingTLSParams) InsecureSkipVerify() refreshable.Bool { + return refreshable.NewBool(r.MapTLSParams(func(i TLSParams) interface{} { + return i.InsecureSkipVerify + })) +} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/metrics.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/metrics.go index 46e1fe74..eb1cf1a0 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/metrics.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/metrics.go @@ -78,44 +78,32 @@ func (s StaticTagsProvider) Tags(_ *http.Request, _ *http.Response, _ error) met return metrics.Tags(s) } -type refreshableMetricsTagsProvider struct { - refreshable.Refreshable // contains metrics.Tags -} - -func (r refreshableMetricsTagsProvider) Tags(_ *http.Request, _ *http.Response, _ error) metrics.Tags { - return r.Current().(metrics.Tags) -} - // MetricsMiddleware updates the "client.response" timer metric on every request. // By default, metrics are tagged with 'service-name', 'method', and 'family' (of the // status code). This metric name and tag set matches http-remoting's DefaultHostMetrics: // https://github.com/palantir/http-remoting/blob/develop/okhttp-clients/src/main/java/com/palantir/remoting3/okhttp/DefaultHostMetrics.java func MetricsMiddleware(serviceName string, tagProviders ...TagsProvider) (Middleware, error) { - serviceNameTag, err := metrics.NewTag(MetricTagServiceName, serviceName) - if err != nil { - return nil, werror.Wrap(err, "failed to construct service-name metric tag", werror.SafeParam("serviceName", serviceName)) - } - return newMetricsMiddleware(serviceNameTag, tagProviders, nil), nil + refreshableName := refreshable.NewString(refreshable.NewDefaultRefreshable(serviceName)) + return newMetricsMiddleware(refreshableName, tagProviders, nil), nil } -func newMetricsMiddleware(serviceNameTag metrics.Tag, tagProviders []TagsProvider, disabled refreshable.Bool) Middleware { +func newMetricsMiddleware(serviceName refreshable.String, tagProviders []TagsProvider, disabled refreshable.Bool) Middleware { return &metricsMiddleware{ - Disabled: disabled, - ServiceNameTag: serviceNameTag, + Disabled: disabled, + ServiceName: serviceName, Tags: append( tagProviders, TagsProviderFunc(tagStatusFamily), TagsProviderFunc(tagRequestMethod), TagsProviderFunc(tagRequestMethodName), - StaticTagsProvider(metrics.Tags{serviceNameTag}), ), } } type metricsMiddleware struct { - Disabled refreshable.Bool - ServiceNameTag metrics.Tag - Tags []TagsProvider + Disabled refreshable.Bool + ServiceName refreshable.String + Tags []TagsProvider } // RoundTrip will emit counter and timer metrics with the name 'mariner.k8sClient.request' @@ -125,15 +113,16 @@ func (h *metricsMiddleware) RoundTrip(req *http.Request, next http.RoundTripper) // If we have a Disabled refreshable and it is true, no-op. return next.RoundTrip(req) } + serviceNameTag := metrics.NewTagWithFallbackValue(MetricTagServiceName, h.ServiceName.CurrentString(), "unknown") - metrics.FromContext(req.Context()).Counter(MetricRequestInFlight, h.ServiceNameTag).Inc(1) + metrics.FromContext(req.Context()).Counter(MetricRequestInFlight, serviceNameTag).Inc(1) start := time.Now() - tlsMetricsContext := h.tlsTraceContext(req.Context()) + tlsMetricsContext := h.tlsTraceContext(req.Context(), serviceNameTag) resp, err := next.RoundTrip(req.WithContext(tlsMetricsContext)) duration := time.Since(start) - metrics.FromContext(req.Context()).Counter(MetricRequestInFlight, h.ServiceNameTag).Dec(1) + metrics.FromContext(req.Context()).Counter(MetricRequestInFlight, serviceNameTag).Dec(1) - var tags metrics.Tags + tags := []metrics.Tag{serviceNameTag} for _, tagProvider := range h.Tags { tags = append(tags, tagProvider.Tags(req, resp, err)...) } @@ -179,20 +168,20 @@ func tagRequestMethodName(req *http.Request, _ *http.Response, _ error) metrics. return metrics.Tags{metrics.MustNewTag(metricRPCMethodName, "RPCMethodNameInvalid")} } -func (h *metricsMiddleware) tlsTraceContext(ctx context.Context) context.Context { +func (h *metricsMiddleware) tlsTraceContext(ctx context.Context, serviceNameTag metrics.Tag) context.Context { return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ GotConn: func(info httptrace.GotConnInfo) { if info.Reused { - metrics.FromContext(ctx).Counter(MetricConnCreate, h.ServiceNameTag, MetricTagConnectionReused).Inc(1) + metrics.FromContext(ctx).Counter(MetricConnCreate, serviceNameTag, MetricTagConnectionReused).Inc(1) } else { - metrics.FromContext(ctx).Counter(MetricConnCreate, h.ServiceNameTag, MetricTagConnectionNew).Inc(1) + metrics.FromContext(ctx).Counter(MetricConnCreate, serviceNameTag, MetricTagConnectionNew).Inc(1) } }, TLSHandshakeStart: func() { - metrics.FromContext(ctx).Meter(MetricTLSHandshakeAttempt, h.ServiceNameTag).Mark(1) + metrics.FromContext(ctx).Meter(MetricTLSHandshakeAttempt, serviceNameTag).Mark(1) }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { - tags := []metrics.Tag{h.ServiceNameTag} + tags := []metrics.Tag{serviceNameTag} cipherSuite := tls.CipherSuiteName(state.CipherSuite) if cipherSuite != "" { tags = append(tags, metrics.MustNewTag(CipherTagKey, cipherSuite)) diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/middleware.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/middleware.go index b5071c55..fe1e314a 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/middleware.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/middleware.go @@ -16,8 +16,6 @@ package httpclient import ( "net/http" - - "github.com/palantir/pkg/refreshable" ) // A Middleware wraps an http client's request and is able to read or modify the request and response. @@ -55,15 +53,3 @@ type wrappedClient struct { func (c *wrappedClient) RoundTrip(req *http.Request) (*http.Response, error) { return c.middleware.RoundTrip(req, c.baseTransport) } - -type conditionalMiddleware struct { - Disabled refreshable.Bool - Delegate Middleware -} - -func (m *conditionalMiddleware) RoundTrip(req *http.Request, next http.RoundTripper) (*http.Response, error) { - if m.Disabled != nil && m.Disabled.CurrentBool() { - return next.RoundTrip(req) - } - return m.Delegate.RoundTrip(req, next) -} diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/trace.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/trace.go index 867b4daa..7481caa1 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/trace.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/trace.go @@ -17,6 +17,7 @@ package httpclient import ( "net/http" + "github.com/palantir/pkg/refreshable" "github.com/palantir/witchcraft-go-tracing/wtracing" "github.com/palantir/witchcraft-go-tracing/wtracing/propagation/b3" ) @@ -26,21 +27,29 @@ import ( // Only if the RPC method name is set does the middleware create a new span (with that name) for the // duration of the request. type traceMiddleware struct { - ServiceName string - CreateRequestSpan bool - InjectHeaders bool + ServiceName refreshable.String + DisableRequestSpan bool + DisableTraceHeaders bool +} + +func newTraceMiddleware(serviceName refreshable.String, disableRequestSpan, disableTraceHeaders bool) traceMiddleware { + return traceMiddleware{ + ServiceName: serviceName, + DisableRequestSpan: disableRequestSpan, + DisableTraceHeaders: disableTraceHeaders, + } } func (t traceMiddleware) RoundTrip(req *http.Request, next http.RoundTripper) (*http.Response, error) { ctx := req.Context() span := wtracing.SpanFromContext(ctx) - if t.CreateRequestSpan { + if !t.DisableRequestSpan { // Create a child span if a method name is set. Otherwise, fall through and just inject the parent span's headers. if method := getRPCMethodName(req.Context()); method != "" { span, ctx = wtracing.StartSpanFromContext(ctx, wtracing.TracerFromContext(ctx), method, wtracing.WithKind(wtracing.Client), - wtracing.WithRemoteEndpoint(&wtracing.Endpoint{ServiceName: t.ServiceName})) + wtracing.WithRemoteEndpoint(&wtracing.Endpoint{ServiceName: t.ServiceName.CurrentString()})) if span != nil { defer span.Finish() } @@ -48,7 +57,7 @@ func (t traceMiddleware) RoundTrip(req *http.Request, next http.RoundTripper) (* } } - if t.InjectHeaders { + if !t.DisableTraceHeaders { if span != nil { b3.SpanInjector(req)(span.Context()) } else { diff --git a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/zz_generated_refreshables.go b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/zz_generated_refreshables.go index 41ef16bc..b5a35ef2 100644 --- a/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/zz_generated_refreshables.go +++ b/vendor/github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/zz_generated_refreshables.go @@ -73,6 +73,8 @@ type RefreshableClientConfig interface { IdleConnTimeout() refreshable.DurationPtr TLSHandshakeTimeout() refreshable.DurationPtr ExpectContinueTimeout() refreshable.DurationPtr + ResponseHeaderTimeout() refreshable.DurationPtr + KeepAlive() refreshable.DurationPtr HTTP2ReadIdleTimeout() refreshable.DurationPtr HTTP2PingTimeout() refreshable.DurationPtr MaxIdleConns() refreshable.IntPtr @@ -207,6 +209,18 @@ func (r RefreshingClientConfig) ExpectContinueTimeout() refreshable.DurationPtr })) } +func (r RefreshingClientConfig) ResponseHeaderTimeout() refreshable.DurationPtr { + return refreshable.NewDurationPtr(r.MapClientConfig(func(i ClientConfig) interface{} { + return i.ResponseHeaderTimeout + })) +} + +func (r RefreshingClientConfig) KeepAlive() refreshable.DurationPtr { + return refreshable.NewDurationPtr(r.MapClientConfig(func(i ClientConfig) interface{} { + return i.KeepAlive + })) +} + func (r RefreshingClientConfig) HTTP2ReadIdleTimeout() refreshable.DurationPtr { return refreshable.NewDurationPtr(r.MapClientConfig(func(i ClientConfig) interface{} { return i.HTTP2ReadIdleTimeout @@ -421,6 +435,7 @@ type RefreshableSecurityConfig interface { CAFiles() refreshable.StringSlice CertFile() refreshable.String KeyFile() refreshable.String + InsecureSkipVerify() refreshable.BoolPtr } type RefreshingSecurityConfig struct { @@ -465,6 +480,12 @@ func (r RefreshingSecurityConfig) KeyFile() refreshable.String { })) } +func (r RefreshingSecurityConfig) InsecureSkipVerify() refreshable.BoolPtr { + return refreshable.NewBoolPtr(r.MapSecurityConfig(func(i SecurityConfig) interface{} { + return i.InsecureSkipVerify + })) +} + type RefreshableStringToClientConfig interface { refreshable.Refreshable CurrentStringToClientConfig() map[string]ClientConfig diff --git a/vendor/modules.txt b/vendor/modules.txt index db5bbbbc..d9129ecb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -20,16 +20,16 @@ github.com/openzipkin/zipkin-go/idgenerator github.com/openzipkin/zipkin-go/model github.com/openzipkin/zipkin-go/propagation github.com/openzipkin/zipkin-go/reporter -# github.com/palantir/conjure-go-runtime/v2 v2.79.0 -## explicit; go 1.21 +# github.com/palantir/conjure-go-runtime/v2 v2.82.0 +## explicit; go 1.22.0 github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal github.com/palantir/conjure-go-runtime/v2/conjure-go-client/httpclient/internal/refreshingclient github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/codecs github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/errors github.com/palantir/conjure-go-runtime/v2/conjure-go-server/httpserver -# github.com/palantir/go-encrypted-config-value v1.36.0 -## explicit; go 1.21 +# github.com/palantir/go-encrypted-config-value v1.37.0 +## explicit; go 1.22.0 github.com/palantir/go-encrypted-config-value/encryptedconfigvalue github.com/palantir/go-encrypted-config-value/encryption # github.com/palantir/go-metrics v1.1.1