Home | History | Annotate | Download | only in httptrace
      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 }
    244