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