trace.go 8.1 KB

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