...
Run Format

Source file src/net/http/httptrace/trace.go

Documentation: net/http/httptrace

  // Copyright 2016 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  // Package httptrace provides mechanisms to trace the events within
  // HTTP client requests.
  package httptrace
  
  import (
  	"context"
  	"crypto/tls"
  	"internal/nettrace"
  	"net"
  	"reflect"
  	"time"
  )
  
  // unique type to prevent assignment.
  type clientEventContextKey struct{}
  
  // ContextClientTrace returns the ClientTrace associated with the
  // provided context. If none, it returns nil.
  func ContextClientTrace(ctx context.Context) *ClientTrace {
  	trace, _ := ctx.Value(clientEventContextKey{}).(*ClientTrace)
  	return trace
  }
  
  // WithClientTrace returns a new context based on the provided parent
  // ctx. HTTP client requests made with the returned context will use
  // the provided trace hooks, in addition to any previous hooks
  // registered with ctx. Any hooks defined in the provided trace will
  // be called first.
  func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
  	if trace == nil {
  		panic("nil trace")
  	}
  	old := ContextClientTrace(ctx)
  	trace.compose(old)
  
  	ctx = context.WithValue(ctx, clientEventContextKey{}, trace)
  	if trace.hasNetHooks() {
  		nt := &nettrace.Trace{
  			ConnectStart: trace.ConnectStart,
  			ConnectDone:  trace.ConnectDone,
  		}
  		if trace.DNSStart != nil {
  			nt.DNSStart = func(name string) {
  				trace.DNSStart(DNSStartInfo{Host: name})
  			}
  		}
  		if trace.DNSDone != nil {
  			nt.DNSDone = func(netIPs []interface{}, coalesced bool, err error) {
  				addrs := make([]net.IPAddr, len(netIPs))
  				for i, ip := range netIPs {
  					addrs[i] = ip.(net.IPAddr)
  				}
  				trace.DNSDone(DNSDoneInfo{
  					Addrs:     addrs,
  					Coalesced: coalesced,
  					Err:       err,
  				})
  			}
  		}
  		ctx = context.WithValue(ctx, nettrace.TraceKey{}, nt)
  	}
  	return ctx
  }
  
  // ClientTrace is a set of hooks to run at various stages of an outgoing
  // HTTP request. Any particular hook may be nil. Functions may be
  // called concurrently from different goroutines and some may be called
  // after the request has completed or failed.
  //
  // ClientTrace currently traces a single HTTP request & response
  // during a single round trip and has no hooks that span a series
  // of redirected requests.
  //
  // See https://blog.golang.org/http-tracing for more.
  type ClientTrace struct {
  	// GetConn is called before a connection is created or
  	// retrieved from an idle pool. The hostPort is the
  	// "host:port" of the target or proxy. GetConn is called even
  	// if there's already an idle cached connection available.
  	GetConn func(hostPort string)
  
  	// GotConn is called after a successful connection is
  	// obtained. There is no hook for failure to obtain a
  	// connection; instead, use the error from
  	// Transport.RoundTrip.
  	GotConn func(GotConnInfo)
  
  	// PutIdleConn is called when the connection is returned to
  	// the idle pool. If err is nil, the connection was
  	// successfully returned to the idle pool. If err is non-nil,
  	// it describes why not. PutIdleConn is not called if
  	// connection reuse is disabled via Transport.DisableKeepAlives.
  	// PutIdleConn is called before the caller's Response.Body.Close
  	// call returns.
  	// For HTTP/2, this hook is not currently used.
  	PutIdleConn func(err error)
  
  	// GotFirstResponseByte is called when the first byte of the response
  	// headers is available.
  	GotFirstResponseByte func()
  
  	// Got100Continue is called if the server replies with a "100
  	// Continue" response.
  	Got100Continue func()
  
  	// DNSStart is called when a DNS lookup begins.
  	DNSStart func(DNSStartInfo)
  
  	// DNSDone is called when a DNS lookup ends.
  	DNSDone func(DNSDoneInfo)
  
  	// ConnectStart is called when a new connection's Dial begins.
  	// If net.Dialer.DualStack (IPv6 "Happy Eyeballs") support is
  	// enabled, this may be called multiple times.
  	ConnectStart func(network, addr string)
  
  	// ConnectDone is called when a new connection's Dial
  	// completes. The provided err indicates whether the
  	// connection completedly successfully.
  	// If net.Dialer.DualStack ("Happy Eyeballs") support is
  	// enabled, this may be called multiple times.
  	ConnectDone func(network, addr string, err error)
  
  	// TLSHandshakeStart is called when the TLS handshake is started. When
  	// connecting to a HTTPS site via a HTTP proxy, the handshake happens after
  	// the CONNECT request is processed by the proxy.
  	TLSHandshakeStart func()
  
  	// TLSHandshakeDone is called after the TLS handshake with either the
  	// successful handshake's connection state, or a non-nil error on handshake
  	// failure.
  	TLSHandshakeDone func(tls.ConnectionState, error)
  
  	// WroteHeaders is called after the Transport has written
  	// the request headers.
  	WroteHeaders func()
  
  	// Wait100Continue is called if the Request specified
  	// "Expected: 100-continue" and the Transport has written the
  	// request headers but is waiting for "100 Continue" from the
  	// server before writing the request body.
  	Wait100Continue func()
  
  	// WroteRequest is called with the result of writing the
  	// request and any body. It may be called multiple times
  	// in the case of retried requests.
  	WroteRequest func(WroteRequestInfo)
  }
  
  // WroteRequestInfo contains information provided to the WroteRequest
  // hook.
  type WroteRequestInfo struct {
  	// Err is any error encountered while writing the Request.
  	Err error
  }
  
  // compose modifies t such that it respects the previously-registered hooks in old,
  // subject to the composition policy requested in t.Compose.
  func (t *ClientTrace) compose(old *ClientTrace) {
  	if old == nil {
  		return
  	}
  	tv := reflect.ValueOf(t).Elem()
  	ov := reflect.ValueOf(old).Elem()
  	structType := tv.Type()
  	for i := 0; i < structType.NumField(); i++ {
  		tf := tv.Field(i)
  		hookType := tf.Type()
  		if hookType.Kind() != reflect.Func {
  			continue
  		}
  		of := ov.Field(i)
  		if of.IsNil() {
  			continue
  		}
  		if tf.IsNil() {
  			tf.Set(of)
  			continue
  		}
  
  		// Make a copy of tf for tf to call. (Otherwise it
  		// creates a recursive call cycle and stack overflows)
  		tfCopy := reflect.ValueOf(tf.Interface())
  
  		// We need to call both tf and of in some order.
  		newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value {
  			tfCopy.Call(args)
  			return of.Call(args)
  		})
  		tv.Field(i).Set(newFunc)
  	}
  }
  
  // DNSStartInfo contains information about a DNS request.
  type DNSStartInfo struct {
  	Host string
  }
  
  // DNSDoneInfo contains information about the results of a DNS lookup.
  type DNSDoneInfo struct {
  	// Addrs are the IPv4 and/or IPv6 addresses found in the DNS
  	// lookup. The contents of the slice should not be mutated.
  	Addrs []net.IPAddr
  
  	// Err is any error that occurred during the DNS lookup.
  	Err error
  
  	// Coalesced is whether the Addrs were shared with another
  	// caller who was doing the same DNS lookup concurrently.
  	Coalesced bool
  }
  
  func (t *ClientTrace) hasNetHooks() bool {
  	if t == nil {
  		return false
  	}
  	return t.DNSStart != nil || t.DNSDone != nil || t.ConnectStart != nil || t.ConnectDone != nil
  }
  
  // GotConnInfo is the argument to the ClientTrace.GotConn function and
  // contains information about the obtained connection.
  type GotConnInfo struct {
  	// Conn is the connection that was obtained. It is owned by
  	// the http.Transport and should not be read, written or
  	// closed by users of ClientTrace.
  	Conn net.Conn
  
  	// Reused is whether this connection has been previously
  	// used for another HTTP request.
  	Reused bool
  
  	// WasIdle is whether this connection was obtained from an
  	// idle pool.
  	WasIdle bool
  
  	// IdleTime reports how long the connection was previously
  	// idle, if WasIdle is true.
  	IdleTime time.Duration
  }
  

View as plain text