github.com/euank/go@v0.0.0-20160829210321-495514729181/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 "internal/nettrace" 12 "net" 13 "reflect" 14 "time" 15 ) 16 17 // unique type to prevent assignment. 18 type clientEventContextKey struct{} 19 20 // ContextClientTrace returns the ClientTrace associated with the 21 // provided context. If none, it returns nil. 22 func ContextClientTrace(ctx context.Context) *ClientTrace { 23 trace, _ := ctx.Value(clientEventContextKey{}).(*ClientTrace) 24 return trace 25 } 26 27 // WithClientTrace returns a new context based on the provided parent 28 // ctx. HTTP client requests made with the returned context will use 29 // the provided trace hooks, in addition to any previous hooks 30 // registered with ctx. Any hooks defined in the provided trace will 31 // be called first. 32 func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context { 33 if trace == nil { 34 panic("nil trace") 35 } 36 old := ContextClientTrace(ctx) 37 trace.compose(old) 38 39 ctx = context.WithValue(ctx, clientEventContextKey{}, trace) 40 if trace.hasNetHooks() { 41 nt := &nettrace.Trace{ 42 ConnectStart: trace.ConnectStart, 43 ConnectDone: trace.ConnectDone, 44 } 45 if trace.DNSStart != nil { 46 nt.DNSStart = func(name string) { 47 trace.DNSStart(DNSStartInfo{Host: name}) 48 } 49 } 50 if trace.DNSDone != nil { 51 nt.DNSDone = func(netIPs []interface{}, coalesced bool, err error) { 52 addrs := make([]net.IPAddr, len(netIPs)) 53 for i, ip := range netIPs { 54 addrs[i] = ip.(net.IPAddr) 55 } 56 trace.DNSDone(DNSDoneInfo{ 57 Addrs: addrs, 58 Coalesced: coalesced, 59 Err: err, 60 }) 61 } 62 } 63 ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt) 64 } 65 return ctx 66 } 67 68 // ClientTrace is a set of hooks to run at various stages of an HTTP 69 // client request. Any particular hook may be nil. Functions may be 70 // called concurrently from different goroutines, starting after the 71 // call to Transport.RoundTrip and ending either when RoundTrip 72 // returns an error, or when the Response.Body is closed. 73 type ClientTrace struct { 74 // GetConn is called before a connection is created or 75 // retrieved from an idle pool. The hostPort is the 76 // "host:port" of the target or proxy. GetConn is called even 77 // if there's already an idle cached connection available. 78 GetConn func(hostPort string) 79 80 // GotConn is called after a successful connection is 81 // obtained. There is no hook for failure to obtain a 82 // connection; instead, use the error from 83 // Transport.RoundTrip. 84 GotConn func(GotConnInfo) 85 86 // PutIdleConn is called when the connection is returned to 87 // the idle pool. If err is nil, the connection was 88 // successfully returned to the idle pool. If err is non-nil, 89 // it describes why not. PutIdleConn is not called if 90 // connection reuse is disabled via Transport.DisableKeepAlives. 91 // PutIdleConn is called before the caller's Response.Body.Close 92 // call returns. 93 // For HTTP/2, this hook is not currently used. 94 PutIdleConn func(err error) 95 96 // GotFirstResponseByte is called when the first byte of the response 97 // headers is available. 98 GotFirstResponseByte func() 99 100 // Got100Continue is called if the server replies with a "100 101 // Continue" response. 102 Got100Continue func() 103 104 // DNSStart is called when a DNS lookup begins. 105 DNSStart func(DNSStartInfo) 106 107 // DNSDone is called when a DNS lookup ends. 108 DNSDone func(DNSDoneInfo) 109 110 // ConnectStart is called when a new connection's Dial begins. 111 // If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is 112 // enabled, this may be called multiple times. 113 ConnectStart func(network, addr string) 114 115 // ConnectDone is called when a new connection's Dial 116 // completes. The provided err indicates whether the 117 // connection completedly successfully. 118 // If net.Dialer.DualStack ("Happy Eyeballs") support is 119 // enabled, this may be called multiple times. 120 ConnectDone func(network, addr string, err error) 121 122 // WroteHeaders is called after the Transport has written 123 // the request headers. 124 WroteHeaders func() 125 126 // Wait100Continue is called if the Request specified 127 // "Expected: 100-continue" and the Transport has written the 128 // request headers but is waiting for "100 Continue" from the 129 // server before writing the request body. 130 Wait100Continue func() 131 132 // WroteRequest is called with the result of writing the 133 // request and any body. 134 WroteRequest func(WroteRequestInfo) 135 } 136 137 // WroteRequestInfo contains information provided to the WroteRequest 138 // hook. 139 type WroteRequestInfo struct { 140 // Err is any error encountered while writing the Request. 141 Err error 142 } 143 144 // compose modifies t such that it respects the previously-registered hooks in old, 145 // subject to the composition policy requested in t.Compose. 146 func (t *ClientTrace) compose(old *ClientTrace) { 147 if old == nil { 148 return 149 } 150 tv := reflect.ValueOf(t).Elem() 151 ov := reflect.ValueOf(old).Elem() 152 structType := tv.Type() 153 for i := 0; i < structType.NumField(); i++ { 154 tf := tv.Field(i) 155 hookType := tf.Type() 156 if hookType.Kind() != reflect.Func { 157 continue 158 } 159 of := ov.Field(i) 160 if of.IsNil() { 161 continue 162 } 163 if tf.IsNil() { 164 tf.Set(of) 165 continue 166 } 167 168 // Make a copy of tf for tf to call. (Otherwise it 169 // creates a recursive call cycle and stack overflows) 170 tfCopy := reflect.ValueOf(tf.Interface()) 171 172 // We need to call both tf and of in some order. 173 newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value { 174 tfCopy.Call(args) 175 return of.Call(args) 176 }) 177 tv.Field(i).Set(newFunc) 178 } 179 } 180 181 // DNSStartInfo contains information about a DNS request. 182 type DNSStartInfo struct { 183 Host string 184 } 185 186 // DNSDoneInfo contains information about the results of a DNS lookup. 187 type DNSDoneInfo struct { 188 // Addrs are the IPv4 and/or IPv6 addresses found in the DNS 189 // lookup. The contents of the slice should not be mutated. 190 Addrs []net.IPAddr 191 192 // Err is any error that occurred during the DNS lookup. 193 Err error 194 195 // Coalesced is whether the Addrs were shared with another 196 // caller who was doing the same DNS lookup concurrently. 197 Coalesced bool 198 } 199 200 func (t *ClientTrace) hasNetHooks() bool { 201 if t == nil { 202 return false 203 } 204 return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil 205 } 206 207 // GotConnInfo is the argument to the ClientTrace.GotConn function and 208 // contains information about the obtained connection. 209 type GotConnInfo struct { 210 // Conn is the connection that was obtained. It is owned by 211 // the http.Transport and should not be read, written or 212 // closed by users of ClientTrace. 213 Conn net.Conn 214 215 // Reused is whether this connection has been previously 216 // used for another HTTP request. 217 Reused bool 218 219 // WasIdle is whether this connection was obtained from an 220 // idle pool. 221 WasIdle bool 222 223 // IdleTime reports how long the connection was previously 224 // idle, if WasIdle is true. 225 IdleTime time.Duration 226 }