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
|
package httptransport
import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"net/http/httptrace"
"strconv"
"strings"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)
var (
sysPoolOnce = &sync.Once{}
sysPool *x509.CertPool
// only overridden by transport_darwin.go
loadExtraCerts = func() {}
// InternalTransport can be used with http.Client with TLS and certificates
InternalTransport = newInternalTransport()
)
type meteredRoundTripper struct {
next http.RoundTripper
name string
tracer *prometheus.HistogramVec
durations *prometheus.HistogramVec
counter *prometheus.CounterVec
}
func newInternalTransport() *http.Transport {
return &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
return tls.Dial(network, addr, &tls.Config{RootCAs: pool()})
},
Proxy: http.ProxyFromEnvironment,
// overrides the DefaultMaxIdleConnsPerHost = 2
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
// Set more timeouts https://gitlab.com/gitlab-org/gitlab-pages/-/issues/495
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
// NewTransportWithMetrics will create a custom http.RoundTripper that can be used with an http.Client.
// The RoundTripper will report metrics based on the collectors passed.
func NewTransportWithMetrics(name string, tracerVec, durationsVec *prometheus.
HistogramVec, counterVec *prometheus.CounterVec) http.RoundTripper {
return &meteredRoundTripper{
next: InternalTransport,
name: name,
tracer: tracerVec,
durations: durationsVec,
counter: counterVec,
}
}
// This is here because macOS does not support the SSL_CERT_FILE and
// SSL_CERT_DIR environment variables. We have arranged things to read
// SSL_CERT_FILE and SSL_CERT_DIR as late as possible to avoid conflicts
// with file descriptor passing at startup.
func pool() *x509.CertPool {
sysPoolOnce.Do(loadPool)
return sysPool
}
func loadPool() {
var err error
// Always load the system cert pool
sysPool, err = x509.SystemCertPool()
if err != nil {
log.WithError(err).Error("failed to load system cert pool for http client")
return
}
// Go does not load SSL_CERT_FILE and SSL_CERT_DIR on darwin systems so we need to
// load them manually in OSX. See https://golang.org/src/crypto/x509/root_unix.go
loadExtraCerts()
}
// withRoundTripper takes an original RoundTripper, reports metrics based on the
// gauge and counter collectors passed
func (mrt *meteredRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
start := time.Now()
r = r.WithContext(httptrace.WithClientTrace(r.Context(), mrt.newTracer(start)))
resp, err := mrt.next.RoundTrip(r)
if err != nil {
mrt.counter.WithLabelValues("error").Inc()
return nil, err
}
mrt.logResponse(r, resp)
statusCode := strconv.Itoa(resp.StatusCode)
mrt.durations.WithLabelValues(statusCode).Observe(time.Since(start).Seconds())
mrt.counter.WithLabelValues(statusCode).Inc()
return resp, nil
}
func (mrt *meteredRoundTripper) logResponse(req *http.Request, resp *http.Response) {
if log.GetLevel() == log.TraceLevel {
l := log.WithFields(log.Fields{
"client_name": mrt.name,
"req_url": req.URL.String(),
"res_status_code": resp.StatusCode,
})
for header, value := range resp.Header {
l = l.WithField(strings.ToLower(header), strings.Join(value, ";"))
}
l.Traceln("response")
}
}
|