/
httping.go
110 lines (88 loc) · 2.89 KB
/
httping.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
package httping
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"time"
)
// Absolute timings since start of request
type HTTPRoundTripTimings struct {
RequestStart time.Time
DNSStart time.Time
DNSDone time.Time
ConnectStart time.Time
ConnectDone time.Time
TLSStart time.Time
TLSDone time.Time
GotFirstResponseByte time.Time
ReadHeaders time.Time
ReadBody time.Time
RequestDone time.Time
}
type HTTPRoundTripDurations struct {
// duration of round-trip parts
Resolve time.Duration
Connect time.Duration
TLS time.Duration
// since FirstResponseByte
Headers time.Duration
// since ReadHeaders
Body time.Duration
// since start of request, for ease
FirstResponseByte time.Duration
Total time.Duration
}
func subIfNotZero(a, b time.Time) time.Duration {
if a.IsZero() {
return time.Duration(0)
} else {
return a.Sub(b)
}
}
func (ht HTTPRoundTripTimings) Durations() HTTPRoundTripDurations {
return HTTPRoundTripDurations{
Resolve: subIfNotZero(ht.DNSDone, ht.DNSStart),
Connect: subIfNotZero(ht.ConnectDone, ht.ConnectStart),
TLS: subIfNotZero(ht.TLSDone, ht.TLSStart),
FirstResponseByte: subIfNotZero(ht.GotFirstResponseByte, ht.RequestStart),
Headers: subIfNotZero(ht.ReadHeaders, ht.GotFirstResponseByte),
Body: subIfNotZero(ht.ReadBody, ht.ReadHeaders),
Total: subIfNotZero(ht.RequestDone, ht.RequestStart),
}
}
func GetRoundTripTimings(c *http.Client, req *http.Request, readBody bool) (HTTPRoundTripTimings, *http.Response, []byte, error) {
timings := HTTPRoundTripTimings{}
if c.Transport == nil {
return timings, nil, nil, errors.New("http.Client.Transport must not be 'nil'")
}
trace := &httptrace.ClientTrace{
DNSStart: func(dsi httptrace.DNSStartInfo) { timings.DNSStart = time.Now() },
DNSDone: func(ddi httptrace.DNSDoneInfo) { timings.DNSDone = time.Now() },
TLSHandshakeStart: func() { timings.TLSStart = time.Now() },
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { timings.TLSDone = time.Now() },
ConnectStart: func(network, addr string) { timings.ConnectStart = time.Now() },
ConnectDone: func(network, addr string, err error) { timings.ConnectDone = time.Now() },
GotFirstResponseByte: func() { timings.GotFirstResponseByte = time.Now() },
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
timings.RequestStart = time.Now()
resp, err := c.Transport.RoundTrip(req)
if err != nil {
return timings, resp, nil, fmt.Errorf("round trip failed: %w", err)
}
defer resp.Body.Close()
timings.ReadHeaders = time.Now()
var body []byte
if readBody {
body, err = io.ReadAll(resp.Body)
if err != nil {
return timings, resp, body, fmt.Errorf("read body failed: %w", err)
}
timings.ReadBody = time.Now()
}
timings.RequestDone = time.Now()
return timings, resp, body, err
}