-
Notifications
You must be signed in to change notification settings - Fork 444
/
http_client.go
134 lines (113 loc) · 3.4 KB
/
http_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package testutils
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"time"
"github.com/onsi/ginkgo/v2"
)
// DefaultHttpClient should be used in tests because it configures a timeout which the http.DefaultClient
// does not have
//
// Please note that when the server response time exceeds the client timeout, you may hit the following error:
//
// "Client.Timeout exceeded while awaiting headers"
//
// The solution would be to increase the client timeout defined below. We chose 2 seconds as a reasonable
// default which allows tests to pass consistently.
var DefaultHttpClient = &http.Client{
Timeout: time.Second * 2,
}
// HttpClientBuilder simplifies the process of generating an http client in tests
type HttpClientBuilder struct {
timeout time.Duration
rootCaCert string
serverName string
proxyProtocolBytes []byte
}
// DefaultClientBuilder returns an HttpClientBuilder with some default values
func DefaultClientBuilder() *HttpClientBuilder {
return &HttpClientBuilder{
timeout: DefaultHttpClient.Timeout,
serverName: "gateway-proxy",
}
}
func (c *HttpClientBuilder) WithTimeout(timeout time.Duration) *HttpClientBuilder {
c.timeout = timeout
return c
}
func (c *HttpClientBuilder) WithProxyProtocolBytes(bytes []byte) *HttpClientBuilder {
c.proxyProtocolBytes = bytes
return c
}
func (c *HttpClientBuilder) WithTLSRootCa(rootCaCert string) *HttpClientBuilder {
c.rootCaCert = rootCaCert
return c
}
func (c *HttpClientBuilder) WithTLSServerName(serverName string) *HttpClientBuilder {
c.serverName = serverName
return c
}
func (c *HttpClientBuilder) Clone() *HttpClientBuilder {
if c == nil {
return nil
}
clone := new(HttpClientBuilder)
clone.timeout = c.timeout
clone.rootCaCert = c.rootCaCert
clone.serverName = c.serverName
clone.proxyProtocolBytes = nil
clone.proxyProtocolBytes = append(clone.proxyProtocolBytes, c.proxyProtocolBytes...)
return clone
}
func (c *HttpClientBuilder) Build() *http.Client {
var (
client http.Client
tlsClientConfig *tls.Config
dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
)
if c.timeout.Seconds() == 0 {
ginkgo.Fail("No timeout set on client")
}
// If the rootCACert is provided, configure the client to use TLS
if c.rootCaCert != "" {
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM([]byte(c.rootCaCert))
if !ok {
ginkgo.Fail("CA Cert is not ok")
}
tlsClientConfig = &tls.Config{
InsecureSkipVerify: false,
ServerName: c.serverName,
RootCAs: caCertPool,
}
}
// If the proxyProtocolBytes are provided, configure the dialContext to prepend
// the bytes at the beginning of the connection
// https://www.haproxy.org/download/1.9/doc/proxy-protocol.txt
if len(c.proxyProtocolBytes) > 0 {
dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
var zeroDialer net.Dialer
connection, err := zeroDialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
// inject proxy protocol bytes
// example: []byte("PROXY TCP4 1.2.3.4 1.2.3.5 443 443\r\n")
_, err = connection.Write(c.proxyProtocolBytes)
if err != nil {
_ = connection.Close()
return nil, err
}
return connection, nil
}
}
client.Transport = &http.Transport{
TLSClientConfig: tlsClientConfig,
DialContext: dialContext,
}
client.Timeout = c.timeout
return &client
}