...

Source file src/github.com/launchdarkly/go-server-sdk/v6/ldhttp/http_transport.go

Documentation: github.com/launchdarkly/go-server-sdk/v6/ldhttp

     1  // Package ldhttp provides internal helper functions for custom HTTP configuration.
     2  //
     3  // Applications will not normally need to use this package. Use the HTTP configuration options provided by
     4  // ldcomponents.HTTPConfiguration() instead.
     5  package ldhttp
     6  
     7  import (
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"errors"
    11  	"fmt"
    12  	"net"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"time"
    17  )
    18  
    19  const defaultConnectTimeout = 10 * time.Second
    20  
    21  type transportExtraOptions struct {
    22  	caCerts        *x509.CertPool
    23  	connectTimeout time.Duration
    24  	proxyURL       *url.URL
    25  }
    26  
    27  // TransportOption is the interface for optional configuration parameters that can be passed to NewHTTPTransport.
    28  type TransportOption interface {
    29  	apply(opts *transportExtraOptions) error
    30  }
    31  
    32  type connectTimeoutOption struct {
    33  	timeout time.Duration
    34  }
    35  
    36  func (o connectTimeoutOption) apply(opts *transportExtraOptions) error {
    37  	opts.connectTimeout = o.timeout
    38  	return nil
    39  }
    40  
    41  // ConnectTimeoutOption specifies the maximum time to wait for a TCP connection, when used with
    42  // NewHTTPTransport.
    43  func ConnectTimeoutOption(timeout time.Duration) TransportOption {
    44  	return connectTimeoutOption{timeout: timeout}
    45  }
    46  
    47  type caCertOption struct {
    48  	certData []byte
    49  }
    50  
    51  func (o caCertOption) apply(opts *transportExtraOptions) error {
    52  	if opts.caCerts == nil {
    53  		var err error
    54  		opts.caCerts, err = x509.SystemCertPool() // this returns a *copy* of the existing CA certs
    55  		if err != nil {
    56  			opts.caCerts = x509.NewCertPool() // COVERAGE: can't simulate this condition in unit tests
    57  		}
    58  	}
    59  	if !opts.caCerts.AppendCertsFromPEM(o.certData) {
    60  		return errors.New("invalid CA certificate data")
    61  	}
    62  	return nil
    63  }
    64  
    65  // CACertOption specifies a CA certificate to be added to the trusted root CA list for HTTPS requests,
    66  // when used with NewHTTPTransport.
    67  func CACertOption(certData []byte) TransportOption {
    68  	return caCertOption{certData: certData}
    69  }
    70  
    71  type caCertFileOption struct {
    72  	filePath string
    73  }
    74  
    75  func (o caCertFileOption) apply(opts *transportExtraOptions) error {
    76  	bytes, err := os.ReadFile(o.filePath)
    77  	if err != nil {
    78  		return fmt.Errorf("can't read CA certificate file: %v", err)
    79  	}
    80  	return caCertOption{certData: bytes}.apply(opts)
    81  }
    82  
    83  // CACertFileOption specifies a CA certificate to be added to the trusted root CA list for HTTPS requests,
    84  // when used with NewHTTPTransport. It reads the certificate data from a file in PEM format.
    85  func CACertFileOption(filePath string) TransportOption {
    86  	return caCertFileOption{filePath: filePath}
    87  }
    88  
    89  // ProxyOption specifies a proxy URL to be used for all requests, when used with NewHTTPTransport.
    90  // This overrides any setting of the HTTP_PROXY, HTTPS_PROXY, or NO_PROXY environment variables.
    91  func ProxyOption(url url.URL) TransportOption {
    92  	return proxyOption{url}
    93  }
    94  
    95  type proxyOption struct {
    96  	url url.URL
    97  }
    98  
    99  func (o proxyOption) apply(opts *transportExtraOptions) error {
   100  	opts.proxyURL = &o.url
   101  	return nil
   102  }
   103  
   104  // NewHTTPTransport creates a customized http.Transport struct using the specified options. It returns both
   105  // the Transport and an associated net.Dialer.
   106  //
   107  // To configure the LaunchDarkly SDK, rather than calling this function directly, it is simpler to use
   108  // the methods provided by ldcomponents.HTTPConfiguration().
   109  func NewHTTPTransport(options ...TransportOption) (*http.Transport, *net.Dialer, error) {
   110  	extraOptions := transportExtraOptions{
   111  		connectTimeout: defaultConnectTimeout,
   112  	}
   113  	for _, o := range options {
   114  		err := o.apply(&extraOptions)
   115  		if err != nil {
   116  			return nil, nil, err
   117  		}
   118  	}
   119  	dialer := &net.Dialer{
   120  		Timeout:   extraOptions.connectTimeout,
   121  		KeepAlive: 1 * time.Minute, // see newStreamProcessor for why we are setting this
   122  	}
   123  	transport := newDefaultTransport()
   124  	transport.DialContext = dialer.DialContext
   125  	if extraOptions.caCerts != nil {
   126  		transport.TLSClientConfig = &tls.Config{RootCAs: extraOptions.caCerts} //nolint:gosec // not setting TLS.MinVersion
   127  	}
   128  	if extraOptions.proxyURL != nil {
   129  		transport.Proxy = http.ProxyURL(extraOptions.proxyURL)
   130  	}
   131  	return transport, dialer, nil
   132  }
   133  
   134  func newDefaultTransport() *http.Transport {
   135  	// The reason we don't just make a copy of http.DefaultTransport is that Transport contains a
   136  	// sync.Mutex, and copying a lock by value is bad.
   137  	return &http.Transport{
   138  		Proxy:                 http.ProxyFromEnvironment,
   139  		MaxIdleConns:          100,
   140  		IdleConnTimeout:       90 * time.Second,
   141  		TLSHandshakeTimeout:   10 * time.Second,
   142  		ExpectContinueTimeout: 1 * time.Second,
   143  	}
   144  }
   145  

View as plain text