Source file src/net/http/httptrace/trace.go
1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package httptrace provides mechanisms to trace the events within 6 // HTTP client requests. 7 package httptrace 8 9 import ( 10 "context" 11 "crypto/tls" 12 "internal/nettrace" 13 "net" 14 "net/textproto" 15 "time" 16 ) 17 18 // unique type to prevent assignment. 19 type clientEventContextKey struct{} 20 21 // ContextClientTrace returns the [ClientTrace] associated with the 22 // provided context. If none, it returns nil. 23 func ContextClientTrace(ctx context.Context) *ClientTrace { 24 trace, _ := ctx.Value(clientEventContextKey{}).(*ClientTrace) 25 return trace 26 } 27 28 // WithClientTrace returns a new context based on the provided parent 29 // ctx. HTTP client requests made with the returned context will use 30 // the provided trace hooks, in addition to any previous hooks 31 // registered with ctx. Any hooks defined in the provided trace will 32 // be called first. 33 func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context { 34 if trace == nil { 35 panic("nil trace") 36 } 37 old := ContextClientTrace(ctx) 38 trace.compose(old) 39 40 ctx = context.WithValue(ctx, clientEventContextKey{}, trace) 41 if trace.hasNetHooks() { 42 nt := &nettrace.Trace{ 43 ConnectStart: trace.ConnectStart, 44 ConnectDone: trace.ConnectDone, 45 } 46 if trace.DNSStart != nil { 47 nt.DNSStart = func(name string) { 48 trace.DNSStart(DNSStartInfo{Host: name}) 49 } 50 } 51 if trace.DNSDone != nil { 52 nt.DNSDone = func(netIPs []any, coalesced bool, err error) { 53 addrs := make([]net.IPAddr, len(netIPs)) 54 for i, ip := range netIPs { 55 addrs[i] = ip.(net.IPAddr) 56 } 57 trace.DNSDone(DNSDoneInfo{ 58 Addrs: addrs, 59 Coalesced: coalesced, 60 Err: err, 61 }) 62 } 63 } 64 ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt) 65 } 66 return ctx 67 } 68 69 // ClientTrace is a set of hooks to run at various stages of an outgoing 70 // HTTP request. Any particular hook may be nil. Functions may be 71 // called concurrently from different goroutines and some may be called 72 // after the request has completed or failed. 73 // 74 // ClientTrace currently traces a single HTTP request & response 75 // during a single round trip and has no hooks that span a series 76 // of redirected requests. 77 // 78 // See https://go.dev/blog/http-tracing for more. 79 type ClientTrace struct { 80 // GetConn is called before a connection is created or 81 // retrieved from an idle pool. The hostPort is the 82 // "host:port" of the target or proxy. GetConn is called even 83 // if there's already an idle cached connection available. 84 GetConn func(hostPort string) 85 86 // GotConn is called after a successful connection is 87 // obtained. There is no hook for failure to obtain a 88 // connection; instead, use the error from 89 // Transport.RoundTrip. 90 GotConn func(GotConnInfo) 91 92 // PutIdleConn is called when the connection is returned to 93 // the idle pool. If err is nil, the connection was 94 // successfully returned to the idle pool. If err is non-nil, 95 // it describes why not. PutIdleConn is not called if 96 // connection reuse is disabled via Transport.DisableKeepAlives. 97 // PutIdleConn is called before the caller's Response.Body.Close 98 // call returns. 99 // For HTTP/2, this hook is not currently used. 100 PutIdleConn func(err error) 101 102 // GotFirstResponseByte is called when the first byte of the response 103 // headers is available. 104 GotFirstResponseByte func() 105 106 // Got100Continue is called if the server replies with a "100 107 // Continue" response. 108 Got100Continue func() 109 110 // Got1xxResponse is called for each 1xx informational response header 111 // returned before the final non-1xx response. Got1xxResponse is called 112 // for "100 Continue" responses, even if Got100Continue is also defined. 113 // If it returns an error, the client request is aborted with that error value. 114 Got1xxResponse func(code int, header textproto.MIMEHeader) error 115 116 // DNSStart is called when a DNS lookup begins. 117 DNSStart func(DNSStartInfo) 118 119 // DNSDone is called when a DNS lookup ends. 120 DNSDone func(DNSDoneInfo) 121 122 // ConnectStart is called when a new connection's Dial begins. 123 // If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is 124 // enabled, this may be called multiple times. 125 ConnectStart func(network, addr string) 126 127 // ConnectDone is called when a new connection's Dial 128 // completes. The provided err indicates whether the 129 // connection completed successfully. 130 // If net.Dialer.DualStack ("Happy Eyeballs") support is 131 // enabled, this may be called multiple times. 132 ConnectDone func(network, addr string, err error) 133 134 // TLSHandshakeStart is called when the TLS handshake is started. When 135 // connecting to an HTTPS site via an HTTP proxy, the handshake happens 136 // after the CONNECT request is processed by the proxy. 137 TLSHandshakeStart func() 138 139 // TLSHandshakeDone is called after the TLS handshake with either the 140 // successful handshake's connection state, or a non-nil error on handshake 141 // failure. 142 TLSHandshakeDone func(tls.ConnectionState, error) 143 144 // WroteHeaderField is called after the Transport has written 145 // each request header. At the time of this call the values 146 // might be buffered and not yet written to the network. 147 WroteHeaderField func(key string, value []string) 148 149 // WroteHeaders is called after the Transport has written 150 // all request headers. 151 WroteHeaders func() 152 153 // Wait100Continue is called if the Request specified 154 // "Expect: 100-continue" and the Transport has written the 155 // request headers but is waiting for "100 Continue" from the 156 // server before writing the request body. 157 Wait100Continue func() 158 159 // WroteRequest is called with the result of writing the 160 // request and any body. It may be called multiple times 161 // in the case of retried requests. 162 WroteRequest func(WroteRequestInfo) 163 } 164 165 // WroteRequestInfo contains information provided to the WroteRequest 166 // hook. 167 type WroteRequestInfo struct { 168 // Err is any error encountered while writing the Request. 169 Err error 170 } 171 172 // compose modifies t such that it respects the previously-registered hooks in old, 173 // subject to the composition policy requested in t.Compose. 174 func (t *ClientTrace) compose(old *ClientTrace) { 175 if old == nil { 176 return 177 } 178 t.GetConn = compose1to0(t.GetConn, old.GetConn) 179 t.GotConn = compose1to0(t.GotConn, old.GotConn) 180 t.PutIdleConn = compose1to0(t.PutIdleConn, old.PutIdleConn) 181 t.GotFirstResponseByte = compose0to0(t.GotFirstResponseByte, old.GotFirstResponseByte) 182 t.Got100Continue = compose0to0(t.Got100Continue, old.Got100Continue) 183 t.Got1xxResponse = compose2to1(t.Got1xxResponse, old.Got1xxResponse) 184 t.DNSStart = compose1to0(t.DNSStart, old.DNSStart) 185 t.DNSDone = compose1to0(t.DNSDone, old.DNSDone) 186 t.ConnectStart = compose2to0(t.ConnectStart, old.ConnectStart) 187 t.ConnectDone = compose3to0(t.ConnectDone, old.ConnectDone) 188 t.TLSHandshakeStart = compose0to0(t.TLSHandshakeStart, old.TLSHandshakeStart) 189 t.TLSHandshakeDone = compose2to0(t.TLSHandshakeDone, old.TLSHandshakeDone) 190 t.WroteHeaderField = compose2to0(t.WroteHeaderField, old.WroteHeaderField) 191 t.WroteHeaders = compose0to0(t.WroteHeaders, old.WroteHeaders) 192 t.Wait100Continue = compose0to0(t.Wait100Continue, old.Wait100Continue) 193 t.WroteRequest = compose1to0(t.WroteRequest, old.WroteRequest) 194 } 195 196 func compose0to0[F func()](f1, f2 F) F { 197 if f1 == nil { 198 return f2 199 } 200 if f2 == nil { 201 return f1 202 } 203 return func() { 204 f1() 205 f2() 206 } 207 } 208 209 func compose1to0[F func(A), A any](f1, f2 F) F { 210 if f1 == nil { 211 return f2 212 } 213 if f2 == nil { 214 return f1 215 } 216 return func(a A) { 217 f1(a) 218 f2(a) 219 } 220 } 221 222 func compose2to0[F func(A, B), A, B any](f1, f2 F) F { 223 if f1 == nil { 224 return f2 225 } 226 if f2 == nil { 227 return f1 228 } 229 return func(a A, b B) { 230 f1(a, b) 231 f2(a, b) 232 } 233 } 234 235 func compose2to1[F func(A, B) R, A, B, R any](f1, f2 F) F { 236 if f1 == nil { 237 return f2 238 } 239 if f2 == nil { 240 return f1 241 } 242 return func(a A, b B) R { 243 f1(a, b) 244 return f2(a, b) 245 } 246 } 247 248 func compose3to0[F func(A, B, C), A, B, C any](f1, f2 F) F { 249 if f1 == nil { 250 return f2 251 } 252 if f2 == nil { 253 return f1 254 } 255 return func(a A, b B, c C) { 256 f1(a, b, c) 257 f2(a, b, c) 258 } 259 } 260 261 // DNSStartInfo contains information about a DNS request. 262 type DNSStartInfo struct { 263 Host string 264 } 265 266 // DNSDoneInfo contains information about the results of a DNS lookup. 267 type DNSDoneInfo struct { 268 // Addrs are the IPv4 and/or IPv6 addresses found in the DNS 269 // lookup. The contents of the slice should not be mutated. 270 Addrs []net.IPAddr 271 272 // Err is any error that occurred during the DNS lookup. 273 Err error 274 275 // Coalesced is whether the Addrs were shared with another 276 // caller who was doing the same DNS lookup concurrently. 277 Coalesced bool 278 } 279 280 func (t *ClientTrace) hasNetHooks() bool { 281 if t == nil { 282 return false 283 } 284 return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil 285 } 286 287 // GotConnInfo is the argument to the [ClientTrace.GotConn] function and 288 // contains information about the obtained connection. 289 type GotConnInfo struct { 290 // Conn is the connection that was obtained. It is owned by 291 // the http.Transport and should not be read, written or 292 // closed by users of ClientTrace. 293 Conn net.Conn 294 295 // Reused is whether this connection has been previously 296 // used for another HTTP request. 297 Reused bool 298 299 // WasIdle is whether this connection was obtained from an 300 // idle pool. 301 WasIdle bool 302 303 // IdleTime reports how long the connection was previously 304 // idle, if WasIdle is true. 305 IdleTime time.Duration 306 } 307