blob: 6af30f78d1f4b99b4b6c7f772761fc889a514a73 [file] [log] [blame]
Dan Willemsen38f2dba2016-07-08 14:54:35 -07001// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
Dan Willemsenebae3022017-01-13 23:01:08 -08003// license that can be found in the LICENSE file.
Dan Willemsen38f2dba2016-07-08 14:54:35 -07004
5// Package httptrace provides mechanisms to trace the events within
6// HTTP client requests.
7package httptrace
8
9import (
10 "context"
Dan Willemsenebae3022017-01-13 23:01:08 -080011 "crypto/tls"
Dan Willemsen38f2dba2016-07-08 14:54:35 -070012 "internal/nettrace"
13 "net"
Dan Willemsenc7413322018-08-27 23:21:26 -070014 "net/textproto"
Dan Willemsen38f2dba2016-07-08 14:54:35 -070015 "reflect"
16 "time"
17)
18
19// unique type to prevent assignment.
20type clientEventContextKey struct{}
21
22// ContextClientTrace returns the ClientTrace associated with the
23// provided context. If none, it returns nil.
24func ContextClientTrace(ctx context.Context) *ClientTrace {
25 trace, _ := ctx.Value(clientEventContextKey{}).(*ClientTrace)
26 return trace
27}
28
29// WithClientTrace returns a new context based on the provided parent
30// ctx. HTTP client requests made with the returned context will use
31// the provided trace hooks, in addition to any previous hooks
32// registered with ctx. Any hooks defined in the provided trace will
33// be called first.
34func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
35 if trace == nil {
36 panic("nil trace")
37 }
38 old := ContextClientTrace(ctx)
39 trace.compose(old)
40
41 ctx = context.WithValue(ctx, clientEventContextKey{}, trace)
42 if trace.hasNetHooks() {
43 nt := &nettrace.Trace{
44 ConnectStart: trace.ConnectStart,
45 ConnectDone: trace.ConnectDone,
46 }
47 if trace.DNSStart != nil {
48 nt.DNSStart = func(name string) {
49 trace.DNSStart(DNSStartInfo{Host: name})
50 }
51 }
52 if trace.DNSDone != nil {
Dan Willemsenbc60c3c2021-12-15 01:09:00 -080053 nt.DNSDone = func(netIPs []any, coalesced bool, err error) {
Dan Willemsen38f2dba2016-07-08 14:54:35 -070054 addrs := make([]net.IPAddr, len(netIPs))
55 for i, ip := range netIPs {
56 addrs[i] = ip.(net.IPAddr)
57 }
58 trace.DNSDone(DNSDoneInfo{
59 Addrs: addrs,
60 Coalesced: coalesced,
61 Err: err,
62 })
63 }
64 }
65 ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt)
66 }
67 return ctx
68}
69
Dan Willemsenebae3022017-01-13 23:01:08 -080070// ClientTrace is a set of hooks to run at various stages of an outgoing
71// HTTP request. Any particular hook may be nil. Functions may be
72// called concurrently from different goroutines and some may be called
73// after the request has completed or failed.
74//
75// ClientTrace currently traces a single HTTP request & response
76// during a single round trip and has no hooks that span a series
77// of redirected requests.
78//
79// See https://blog.golang.org/http-tracing for more.
Dan Willemsen38f2dba2016-07-08 14:54:35 -070080type ClientTrace struct {
81 // GetConn is called before a connection is created or
82 // retrieved from an idle pool. The hostPort is the
83 // "host:port" of the target or proxy. GetConn is called even
84 // if there's already an idle cached connection available.
85 GetConn func(hostPort string)
86
87 // GotConn is called after a successful connection is
88 // obtained. There is no hook for failure to obtain a
89 // connection; instead, use the error from
90 // Transport.RoundTrip.
91 GotConn func(GotConnInfo)
92
93 // PutIdleConn is called when the connection is returned to
94 // the idle pool. If err is nil, the connection was
95 // successfully returned to the idle pool. If err is non-nil,
96 // it describes why not. PutIdleConn is not called if
97 // connection reuse is disabled via Transport.DisableKeepAlives.
98 // PutIdleConn is called before the caller's Response.Body.Close
99 // call returns.
100 // For HTTP/2, this hook is not currently used.
101 PutIdleConn func(err error)
102
103 // GotFirstResponseByte is called when the first byte of the response
104 // headers is available.
105 GotFirstResponseByte func()
106
107 // Got100Continue is called if the server replies with a "100
108 // Continue" response.
109 Got100Continue func()
110
Dan Willemsenc7413322018-08-27 23:21:26 -0700111 // Got1xxResponse is called for each 1xx informational response header
112 // returned before the final non-1xx response. Got1xxResponse is called
113 // for "100 Continue" responses, even if Got100Continue is also defined.
114 // If it returns an error, the client request is aborted with that error value.
115 Got1xxResponse func(code int, header textproto.MIMEHeader) error
116
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700117 // DNSStart is called when a DNS lookup begins.
118 DNSStart func(DNSStartInfo)
119
120 // DNSDone is called when a DNS lookup ends.
121 DNSDone func(DNSDoneInfo)
122
123 // ConnectStart is called when a new connection's Dial begins.
124 // If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is
125 // enabled, this may be called multiple times.
126 ConnectStart func(network, addr string)
127
128 // ConnectDone is called when a new connection's Dial
129 // completes. The provided err indicates whether the
Dan Willemsencc753b72021-08-31 13:25:42 -0700130 // connection completed successfully.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700131 // If net.Dialer.DualStack ("Happy Eyeballs") support is
132 // enabled, this may be called multiple times.
133 ConnectDone func(network, addr string, err error)
134
Dan Willemsenebae3022017-01-13 23:01:08 -0800135 // TLSHandshakeStart is called when the TLS handshake is started. When
Patrice Arruda748609c2020-06-25 12:12:21 -0700136 // connecting to an HTTPS site via an HTTP proxy, the handshake happens
137 // after the CONNECT request is processed by the proxy.
Dan Willemsenebae3022017-01-13 23:01:08 -0800138 TLSHandshakeStart func()
139
140 // TLSHandshakeDone is called after the TLS handshake with either the
141 // successful handshake's connection state, or a non-nil error on handshake
142 // failure.
143 TLSHandshakeDone func(tls.ConnectionState, error)
144
Dan Willemsenc7413322018-08-27 23:21:26 -0700145 // WroteHeaderField is called after the Transport has written
146 // each request header. At the time of this call the values
147 // might be buffered and not yet written to the network.
148 WroteHeaderField func(key string, value []string)
149
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700150 // WroteHeaders is called after the Transport has written
Dan Willemsenc7413322018-08-27 23:21:26 -0700151 // all request headers.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700152 WroteHeaders func()
153
154 // Wait100Continue is called if the Request specified
Colin Crossd9c6b802019-03-19 21:10:31 -0700155 // "Expect: 100-continue" and the Transport has written the
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700156 // request headers but is waiting for "100 Continue" from the
157 // server before writing the request body.
158 Wait100Continue func()
159
160 // WroteRequest is called with the result of writing the
Dan Willemsenebae3022017-01-13 23:01:08 -0800161 // request and any body. It may be called multiple times
162 // in the case of retried requests.
Dan Willemsen38f2dba2016-07-08 14:54:35 -0700163 WroteRequest func(WroteRequestInfo)
164}
165
166// WroteRequestInfo contains information provided to the WroteRequest
167// hook.
168type WroteRequestInfo struct {
169 // Err is any error encountered while writing the Request.
170 Err error
171}
172
173// compose modifies t such that it respects the previously-registered hooks in old,
174// subject to the composition policy requested in t.Compose.
175func (t *ClientTrace) compose(old *ClientTrace) {
176 if old == nil {
177 return
178 }
179 tv := reflect.ValueOf(t).Elem()
180 ov := reflect.ValueOf(old).Elem()
181 structType := tv.Type()
182 for i := 0; i < structType.NumField(); i++ {
183 tf := tv.Field(i)
184 hookType := tf.Type()
185 if hookType.Kind() != reflect.Func {
186 continue
187 }
188 of := ov.Field(i)
189 if of.IsNil() {
190 continue
191 }
192 if tf.IsNil() {
193 tf.Set(of)
194 continue
195 }
196
197 // Make a copy of tf for tf to call. (Otherwise it
198 // creates a recursive call cycle and stack overflows)
199 tfCopy := reflect.ValueOf(tf.Interface())
200
201 // We need to call both tf and of in some order.
202 newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value {
203 tfCopy.Call(args)
204 return of.Call(args)
205 })
206 tv.Field(i).Set(newFunc)
207 }
208}
209
210// DNSStartInfo contains information about a DNS request.
211type DNSStartInfo struct {
212 Host string
213}
214
215// DNSDoneInfo contains information about the results of a DNS lookup.
216type DNSDoneInfo struct {
217 // Addrs are the IPv4 and/or IPv6 addresses found in the DNS
218 // lookup. The contents of the slice should not be mutated.
219 Addrs []net.IPAddr
220
221 // Err is any error that occurred during the DNS lookup.
222 Err error
223
224 // Coalesced is whether the Addrs were shared with another
225 // caller who was doing the same DNS lookup concurrently.
226 Coalesced bool
227}
228
229func (t *ClientTrace) hasNetHooks() bool {
230 if t == nil {
231 return false
232 }
233 return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil
234}
235
236// GotConnInfo is the argument to the ClientTrace.GotConn function and
237// contains information about the obtained connection.
238type GotConnInfo struct {
239 // Conn is the connection that was obtained. It is owned by
240 // the http.Transport and should not be read, written or
241 // closed by users of ClientTrace.
242 Conn net.Conn
243
244 // Reused is whether this connection has been previously
245 // used for another HTTP request.
246 Reused bool
247
248 // WasIdle is whether this connection was obtained from an
249 // idle pool.
250 WasIdle bool
251
252 // IdleTime reports how long the connection was previously
253 // idle, if WasIdle is true.
254 IdleTime time.Duration
255}