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