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 "reflect" 16 "time" 17 ) 18 19 // unique type to prevent assignment. 20 type clientEventContextKey struct{} 21 22 // ContextClientTrace returns the [ClientTrace] associated with the 23 // provided context. If none, it returns nil. 24 func 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. 34 func 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 { 53 nt.DNSDone = func(netIPs []any, coalesced bool, err error) { 54 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 70 // 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. 80 type 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 111 // 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 117 // 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 130 // connection completed successfully. 131 // 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 135 // TLSHandshakeStart is called when the TLS handshake is started. When 136 // connecting to an HTTPS site via an HTTP proxy, the handshake happens 137 // after the CONNECT request is processed by the proxy. 138 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 145 // 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 150 // WroteHeaders is called after the Transport has written 151 // all request headers. 152 WroteHeaders func() 153 154 // Wait100Continue is called if the Request specified 155 // "Expect: 100-continue" and the Transport has written the 156 // 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 161 // request and any body. It may be called multiple times 162 // in the case of retried requests. 163 WroteRequest func(WroteRequestInfo) 164 } 165 166 // WroteRequestInfo contains information provided to the WroteRequest 167 // hook. 168 type 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. 175 func (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. 211 type DNSStartInfo struct { 212 Host string 213 } 214 215 // DNSDoneInfo contains information about the results of a DNS lookup. 216 type 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 229 func (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. 238 type 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 } 256