github.com/OpsMx/go-app-base@v0.0.24/httputil/http.go (about) 1 // Copyright 2022 OpsMx, Inc 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httputil 16 17 import ( 18 "crypto/tls" 19 "net" 20 "net/http" 21 "time" 22 23 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 24 ) 25 26 // ClientConfig defines various timeouts we will want to change. 27 // All times are in seconds. If 0, a default will be used. 28 type ClientConfig struct { 29 DialTimeout int `json:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty"` 30 ClientTimeout int `json:"clientTimeout,omitempty" yaml:"clientTimeout,omitempty"` 31 TLSHandshakeTimeout int `json:"tlsHandshakeTimeout,omitempty" yaml:"tlsHandshakeTimeout,omitempty"` 32 ResponseHeaderTimeout int `json:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty"` 33 MaxIdleConnections int `json:"maxIdleConnections,omitempty" yaml:"maxIdleConnections,omitempty"` 34 } 35 36 var defaultTLSConfig *tls.Config 37 38 var defaultClientConfig = &ClientConfig{ 39 DialTimeout: 15, 40 ClientTimeout: 60, 41 TLSHandshakeTimeout: 15, 42 ResponseHeaderTimeout: 60, 43 MaxIdleConnections: 5, 44 } 45 46 func (c *ClientConfig) applyDefaults() { 47 if c.DialTimeout == 0 { 48 c.DialTimeout = defaultClientConfig.DialTimeout 49 } 50 if c.ClientTimeout == 0 { 51 c.ClientTimeout = defaultClientConfig.ClientTimeout 52 } 53 if c.TLSHandshakeTimeout == 0 { 54 c.TLSHandshakeTimeout = defaultClientConfig.TLSHandshakeTimeout 55 } 56 if c.ResponseHeaderTimeout == 0 { 57 c.ResponseHeaderTimeout = defaultClientConfig.ResponseHeaderTimeout 58 } 59 if c.MaxIdleConnections == 0 { 60 c.MaxIdleConnections = defaultClientConfig.MaxIdleConnections 61 } 62 } 63 64 // SetClientConfig will replace the current clientConfig for all future clients 65 // returned by NewHTTPClient(). Generally, this will be set once, and probably 66 // not changed per connection. It is not going to be thread-safe, in that 67 // setting the config and then calling NewHTTPClient() could be a race. 68 func SetClientConfig(c ClientConfig) { 69 if defaultClientConfig == nil { 70 defaultClientConfig = &ClientConfig{} 71 } 72 *defaultClientConfig = c 73 defaultClientConfig.applyDefaults() 74 } 75 76 // SetTLSConfig sets the default TLS configuration used by NewHTTPClient(). 77 // This will generally be set once for adding custom CA roots or other 78 // configuration used throughout the application. 79 // 80 // NewHTTPClient() also allows per-client TLS configuration, if desired. 81 func SetTLSConfig(tlsconfig *tls.Config) { 82 defaultTLSConfig = tlsconfig 83 } 84 85 // NewHTTPClient returns a new http.Client that is configured with 86 // sane timeouts, a global TLS configuration, and optionally a per-client 87 // TLS config. 88 // 89 // Generally, the global config will have things like custom CA roots, 90 // and we will want to trust those for every outgoing conneciton. 91 // A per-client TLS config would be used where we are talking to a 92 // specific API, and want to insert our certificates or a custom 93 // CA root for just that connection. 94 // 95 // Future changes should allow merging tls configs, so we can add to 96 // the base default rather than replace it entirely. 97 func NewHTTPClient(tlsConfig *tls.Config) *http.Client { 98 if tlsConfig == nil { 99 tlsConfig = defaultTLSConfig 100 } 101 dialer := net.Dialer{Timeout: time.Duration(defaultClientConfig.DialTimeout) * time.Second} 102 client := &http.Client{ 103 Timeout: time.Duration(defaultClientConfig.ClientTimeout) * time.Second, 104 Transport: otelhttp.NewTransport(&http.Transport{ 105 Dial: dialer.Dial, 106 DialContext: dialer.DialContext, 107 TLSHandshakeTimeout: time.Duration(defaultClientConfig.TLSHandshakeTimeout) * time.Second, 108 TLSClientConfig: tlsConfig, 109 ResponseHeaderTimeout: time.Duration(defaultClientConfig.ResponseHeaderTimeout) * time.Second, 110 ExpectContinueTimeout: time.Second, 111 MaxIdleConns: defaultClientConfig.MaxIdleConnections, 112 }), 113 CheckRedirect: func(req *http.Request, via []*http.Request) error { 114 return http.ErrUseLastResponse 115 }, 116 } 117 return client 118 }