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