doc: Go doc: httptrace |
|
1) HTTP Request/Response - Trace Events
2) HTTP Persitent Connections
|
|
package main |
|
import ( "fmt" "io" "io/ioutil" "log" "net/http" "net/http/httptrace" "time" ) |
|
tracer for time tracking |
type Tracer struct { start time.Time end time.Time } |
func (t *Tracer) GetConn(hostPort string) { fmt.Printf("GetConn(%s) %d ms\n", hostPort, time.Since(t.start).Milliseconds()) } func (t *Tracer) DNSStart(info httptrace.DNSStartInfo) { fmt.Printf("DNSStart(%+v) %d ms\n", info, time.Since(t.start).Milliseconds()) } func (t *Tracer) DNSDone(info httptrace.DNSDoneInfo) { fmt.Printf("DNSDone(%+v) %d ms\n", info, time.Since(t.start).Milliseconds()) } func (t *Tracer) ConnectStart(network, addr string) { fmt.Printf("ConnectStart(%s, %s) %d ms\n", network, addr, time.Since(t.start).Milliseconds()) } func (t *Tracer) ConnectDone(network, addr string, err error) { fmt.Printf("ConnectDone(%s, %s, %v) %d ms\n", network, addr, err, time.Since(t.start).Milliseconds()) } func (t *Tracer) GotConn(info httptrace.GotConnInfo) { fmt.Printf("GotConn(%+v) %d ms\n", info, time.Since(t.start).Milliseconds()) } func (t *Tracer) GotFirstResponseByte() { t.end = time.Now() fmt.Printf("GotFirstResponseByte (delay) %d ms\n", time.Since(t.start).Milliseconds()) } |
|
func (t *Tracer) PutIdleConn(err error) { t.end = time.Now() fmt.Printf("PutIdleConn(%+v) %d ms\n", err, time.Since(t.start).Milliseconds()) } |
|
Create Trace Info |
func createTrace() *httptrace.ClientTrace { tracer := Tracer{start: time.Now()} trace := &httptrace.ClientTrace{ GetConn: tracer.GetConn, DNSStart: tracer.DNSStart, DNSDone: tracer.DNSDone, ConnectStart: tracer.ConnectStart, ConnectDone: tracer.ConnectDone, GotConn: tracer.GotConn, GotFirstResponseByte: tracer.GotFirstResponseByte, |
called when connection is put back to connection pool |
PutIdleConn: tracer.PutIdleConn, } return trace } |
const url = "http://example.com" |
|
func main() { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { log.Fatal(err) } trace := createTrace() |
|
Create Trace context |
ctx := httptrace.WithClientTrace(req.Context(), trace) |
Attach Trace context to request |
req = req.WithContext(ctx) |
fmt.Println("1st Request to", url) fmt.Println("") |
|
res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } |
|
read the whole body and close so that the underlying TCP conn is re-used |
io.Copy(ioutil.Discard, res.Body) res.Body.Close() |
Create Trace context |
fmt.Println("") fmt.Println("2nd Request to", url) fmt.Println("") req2, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { log.Fatal(err) } trace = createTrace() |
ctx = httptrace.WithClientTrace(req2.Context(), trace) |
|
Attach Trace context to request |
req = req.WithContext(ctx) _, err = http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } } |
$ go run http-trace.go 1st Request to http://example.com |
|
GetConn(example.com:80) 0 ms DNSStart({Host:example.com}) 0 ms DNSDone({Addrs:[{IP:2606:2800:220:1:248:1893:25c8:1946 Zone:} {IP:93.184.216.34 Zone:}] Err:<nil> Coalesced:false}) 11 ms ConnectStart(tcp, [2606:2800:220:1:248:1893:25c8:1946]:80) 11 ms ConnectDone(tcp, [2606:2800:220:1:248:1893:25c8:1946]:80, <nil>) 152 ms GotConn({Conn:0x14000094000 Reused:false WasIdle:false IdleTime:0s}) 152 ms GotFirstResponseByte (delay) 293 ms PutIdleConn(<nil>) 294 ms |
|
2nd Request to http://example.com
|
|
GetConn(example.com:80) 0 ms GotConn({Conn:0x14000094000 Reused:true WasIdle:true IdleTime:152.916µs}) 0 ms GotFirstResponseByte (delay) 140 ms |
|
for 1st Request, we can see PutIdleConn is called. for 2nd Request, DNS resolving part is skipped cuz it reused the previous connection. |
Next example: sync.RWMutex.