master
go 146 lines 4.03 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package web
4
5 import (
6 "context"
7 "crypto/tls"
8 "errors"
9 "fmt"
10 "net"
11 "net/http"
12 "net/url"
13
14 "golang.org/x/net/http2"
15
16 "github.com/netdata/netdata/go/plugins/pkg/confopt"
17 "github.com/netdata/netdata/go/plugins/pkg/tlscfg"
18 )
19
20 // ErrRedirectAttempted indicates that a redirect occurred.
21 var ErrRedirectAttempted = errors.New("redirect")
22
23 // ClientConfig is the configuration of the HTTPConfig client.
24 // This structure is not intended to be used directly as part of a module's configuration.
25 // Supported configuration file formats: YAML.
26 type ClientConfig struct {
27 // Timeout specifies a time limit for requests made by this ClientConfig.
28 // Default (zero value) is no timeout. Must be set before http.Client creation.
29 Timeout confopt.Duration `yaml:"timeout,omitempty" json:"timeout"`
30
31 // NotFollowRedirect specifies the policy for handling redirects.
32 // Default (zero value) is std http package default policy (stop after 10 consecutive requests).
33 NotFollowRedirect bool `yaml:"not_follow_redirects,omitempty" json:"not_follow_redirects"`
34
35 // ProxyURL specifies the URL of the proxy to use. An empty string means use the environment variables
36 // HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions thereof) to get the URL.
37 ProxyURL string `yaml:"proxy_url,omitempty" json:"proxy_url"`
38
39 // TLSConfig specifies the TLS configuration.
40 tlscfg.TLSConfig `yaml:",inline" json:""`
41
42 ForceHTTP2 bool `yaml:"force_http2,omitempty" json:"force_http2"`
43 }
44
45 // NewHTTPClient returns a new *http.Client given a ClientConfig configuration and an error if any.
46 func NewHTTPClient(cfg ClientConfig) (*http.Client, error) {
47 var transport http.RoundTripper
48 var err error
49
50 if cfg.ForceHTTP2 {
51 transport, err = newHTTP2Transport(cfg)
52 } else {
53 transport, err = newHTTPTransport(cfg)
54 }
55 if err != nil {
56 return nil, err
57 }
58
59 client := &http.Client{
60 Timeout: cfg.Timeout.Duration(),
61 Transport: transport,
62 CheckRedirect: redirectFunc(cfg.NotFollowRedirect),
63 }
64
65 return client, nil
66 }
67
68 func newHTTPTransport(cfg ClientConfig) (*http.Transport, error) {
69 tlsConfig, err := tlscfg.NewTLSConfig(cfg.TLSConfig)
70 if err != nil {
71 return nil, fmt.Errorf("error on creating TLS config: %w", err)
72 }
73
74 if cfg.ProxyURL != "" {
75 if _, err := url.Parse(cfg.ProxyURL); err != nil {
76 return nil, fmt.Errorf("error on parsing proxy URL '%s': %w", cfg.ProxyURL, err)
77 }
78 }
79
80 d := &net.Dialer{Timeout: cfg.Timeout.Duration()}
81
82 transport := &http.Transport{
83 TLSClientConfig: tlsConfig,
84 DialContext: d.DialContext,
85 TLSHandshakeTimeout: cfg.Timeout.Duration(),
86 Proxy: proxyFunc(cfg.ProxyURL),
87 }
88
89 return transport, nil
90 }
91
92 func newHTTP2Transport(cfg ClientConfig) (*http2Transport, error) {
93 tlsConfig, err := tlscfg.NewTLSConfig(cfg.TLSConfig)
94 if err != nil {
95 return nil, fmt.Errorf("error on creating TLS config: %w", err)
96 }
97
98 d := &net.Dialer{Timeout: cfg.Timeout.Duration()}
99
100 transport := &http2Transport{
101 t2: &http2.Transport{
102 TLSClientConfig: tlsConfig,
103 },
104 t2c: &http2.Transport{
105 AllowHTTP: true,
106 DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
107 return d.DialContext(ctx, network, addr)
108 },
109 TLSClientConfig: tlsConfig,
110 },
111 }
112
113 return transport, nil
114 }
115
116 type http2Transport struct {
117 t2 *http2.Transport
118 t2c *http2.Transport
119 }
120
121 func (t *http2Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
122 if req.URL.Scheme == "https" {
123 return t.t2.RoundTrip(req)
124 }
125 return t.t2c.RoundTrip(req)
126 }
127
128 func (t *http2Transport) CloseIdleConnections() {
129 t.t2.CloseIdleConnections()
130 t.t2c.CloseIdleConnections()
131 }
132
133 func proxyFunc(rawProxyURL string) func(r *http.Request) (*url.URL, error) {
134 if rawProxyURL == "" {
135 return http.ProxyFromEnvironment
136 }
137 proxyURL, _ := url.Parse(rawProxyURL)
138 return http.ProxyURL(proxyURL)
139 }
140
141 func redirectFunc(notFollow bool) func(req *http.Request, via []*http.Request) error {
142 if notFollow {
143 return func(_ *http.Request, _ []*http.Request) error { return ErrRedirectAttempted }
144 }
145 return nil
146 }