// Package ldhttp provides internal helper functions for custom HTTP configuration. // // Applications will not normally need to use this package. Use the HTTP configuration options provided by // ldcomponents.HTTPConfiguration() instead. package ldhttp import ( "crypto/tls" "crypto/x509" "errors" "fmt" "net" "net/http" "net/url" "os" "time" ) const defaultConnectTimeout = 10 * time.Second type transportExtraOptions struct { caCerts *x509.CertPool connectTimeout time.Duration proxyURL *url.URL } // TransportOption is the interface for optional configuration parameters that can be passed to NewHTTPTransport. type TransportOption interface { apply(opts *transportExtraOptions) error } type connectTimeoutOption struct { timeout time.Duration } func (o connectTimeoutOption) apply(opts *transportExtraOptions) error { opts.connectTimeout = o.timeout return nil } // ConnectTimeoutOption specifies the maximum time to wait for a TCP connection, when used with // NewHTTPTransport. func ConnectTimeoutOption(timeout time.Duration) TransportOption { return connectTimeoutOption{timeout: timeout} } type caCertOption struct { certData []byte } func (o caCertOption) apply(opts *transportExtraOptions) error { if opts.caCerts == nil { var err error opts.caCerts, err = x509.SystemCertPool() // this returns a *copy* of the existing CA certs if err != nil { opts.caCerts = x509.NewCertPool() // COVERAGE: can't simulate this condition in unit tests } } if !opts.caCerts.AppendCertsFromPEM(o.certData) { return errors.New("invalid CA certificate data") } return nil } // CACertOption specifies a CA certificate to be added to the trusted root CA list for HTTPS requests, // when used with NewHTTPTransport. func CACertOption(certData []byte) TransportOption { return caCertOption{certData: certData} } type caCertFileOption struct { filePath string } func (o caCertFileOption) apply(opts *transportExtraOptions) error { bytes, err := os.ReadFile(o.filePath) if err != nil { return fmt.Errorf("can't read CA certificate file: %v", err) } return caCertOption{certData: bytes}.apply(opts) } // CACertFileOption specifies a CA certificate to be added to the trusted root CA list for HTTPS requests, // when used with NewHTTPTransport. It reads the certificate data from a file in PEM format. func CACertFileOption(filePath string) TransportOption { return caCertFileOption{filePath: filePath} } // ProxyOption specifies a proxy URL to be used for all requests, when used with NewHTTPTransport. // This overrides any setting of the HTTP_PROXY, HTTPS_PROXY, or NO_PROXY environment variables. func ProxyOption(url url.URL) TransportOption { return proxyOption{url} } type proxyOption struct { url url.URL } func (o proxyOption) apply(opts *transportExtraOptions) error { opts.proxyURL = &o.url return nil } // NewHTTPTransport creates a customized http.Transport struct using the specified options. It returns both // the Transport and an associated net.Dialer. // // To configure the LaunchDarkly SDK, rather than calling this function directly, it is simpler to use // the methods provided by ldcomponents.HTTPConfiguration(). func NewHTTPTransport(options ...TransportOption) (*http.Transport, *net.Dialer, error) { extraOptions := transportExtraOptions{ connectTimeout: defaultConnectTimeout, } for _, o := range options { err := o.apply(&extraOptions) if err != nil { return nil, nil, err } } dialer := &net.Dialer{ Timeout: extraOptions.connectTimeout, KeepAlive: 1 * time.Minute, // see newStreamProcessor for why we are setting this } transport := newDefaultTransport() transport.DialContext = dialer.DialContext if extraOptions.caCerts != nil { transport.TLSClientConfig = &tls.Config{RootCAs: extraOptions.caCerts} //nolint:gosec // not setting TLS.MinVersion } if extraOptions.proxyURL != nil { transport.Proxy = http.ProxyURL(extraOptions.proxyURL) } return transport, dialer, nil } func newDefaultTransport() *http.Transport { // The reason we don't just make a copy of http.DefaultTransport is that Transport contains a // sync.Mutex, and copying a lock by value is bad. return &http.Transport{ Proxy: http.ProxyFromEnvironment, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } }