github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/net/http/httptrace/trace.go (about) 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 "reflect" 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 []interface{}, 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://blog.golang.org/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 // DNSStart is called when a DNS lookup begins. 111 DNSStart func(DNSStartInfo) 112 113 // DNSDone is called when a DNS lookup ends. 114 DNSDone func(DNSDoneInfo) 115 116 // ConnectStart is called when a new connection's Dial begins. 117 // If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is 118 // enabled, this may be called multiple times. 119 ConnectStart func(network, addr string) 120 121 // ConnectDone is called when a new connection's Dial 122 // completes. The provided err indicates whether the 123 // connection completedly successfully. 124 // If net.Dialer.DualStack ("Happy Eyeballs") support is 125 // enabled, this may be called multiple times. 126 ConnectDone func(network, addr string, err error) 127 128 // TLSHandshakeStart is called when the TLS handshake is started. When 129 // connecting to a HTTPS site via a HTTP proxy, the handshake happens after 130 // the CONNECT request is processed by the proxy. 131 TLSHandshakeStart func() 132 133 // TLSHandshakeDone is called after the TLS handshake with either the 134 // successful handshake's connection state, or a non-nil error on handshake 135 // failure. 136 TLSHandshakeDone func(tls.ConnectionState, error) 137 138 // WroteHeaders is called after the Transport has written 139 // the request headers. 140 WroteHeaders func() 141 142 // Wait100Continue is called if the Request specified 143 // "Expected: 100-continue" and the Transport has written the 144 // request headers but is waiting for "100 Continue" from the 145 // server before writing the request body. 146 Wait100Continue func() 147 148 // WroteRequest is called with the result of writing the 149 // request and any body. It may be called multiple times 150 // in the case of retried requests. 151 WroteRequest func(WroteRequestInfo) 152 } 153 154 // WroteRequestInfo contains information provided to the WroteRequest 155 // hook. 156 type WroteRequestInfo struct { 157 // Err is any error encountered while writing the Request. 158 Err error 159 } 160 161 // compose modifies t such that it respects the previously-registered hooks in old, 162 // subject to the composition policy requested in t.Compose. 163 func (t *ClientTrace) compose(old *ClientTrace) { 164 if old == nil { 165 return 166 } 167 tv := reflect.ValueOf(t).Elem() 168 ov := reflect.ValueOf(old).Elem() 169 structType := tv.Type() 170 for i := 0; i < structType.NumField(); i++ { 171 tf := tv.Field(i) 172 hookType := tf.Type() 173 if hookType.Kind() != reflect.Func { 174 continue 175 } 176 of := ov.Field(i) 177 if of.IsNil() { 178 continue 179 } 180 if tf.IsNil() { 181 tf.Set(of) 182 continue 183 } 184 185 // Make a copy of tf for tf to call. (Otherwise it 186 // creates a recursive call cycle and stack overflows) 187 tfCopy := reflect.ValueOf(tf.Interface()) 188 189 // We need to call both tf and of in some order. 190 newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value { 191 tfCopy.Call(args) 192 return of.Call(args) 193 }) 194 tv.Field(i).Set(newFunc) 195 } 196 } 197 198 // DNSStartInfo contains information about a DNS request. 199 type DNSStartInfo struct { 200 Host string 201 } 202 203 // DNSDoneInfo contains information about the results of a DNS lookup. 204 type DNSDoneInfo struct { 205 // Addrs are the IPv4 and/or IPv6 addresses found in the DNS 206 // lookup. The contents of the slice should not be mutated. 207 Addrs []net.IPAddr 208 209 // Err is any error that occurred during the DNS lookup. 210 Err error 211 212 // Coalesced is whether the Addrs were shared with another 213 // caller who was doing the same DNS lookup concurrently. 214 Coalesced bool 215 } 216 217 func (t *ClientTrace) hasNetHooks() bool { 218 if t == nil { 219 return false 220 } 221 return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil 222 } 223 224 // GotConnInfo is the argument to the ClientTrace.GotConn function and 225 // contains information about the obtained connection. 226 type GotConnInfo struct { 227 // Conn is the connection that was obtained. It is owned by 228 // the http.Transport and should not be read, written or 229 // closed by users of ClientTrace. 230 Conn net.Conn 231 232 // Reused is whether this connection has been previously 233 // used for another HTTP request. 234 Reused bool 235 236 // WasIdle is whether this connection was obtained from an 237 // idle pool. 238 WasIdle bool 239 240 // IdleTime reports how long the connection was previously 241 // idle, if WasIdle is true. 242 IdleTime time.Duration 243 }