github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/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.h
     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  }