author | Brad Fitzpatrick
<bradfitz@golang.org> 2016-05-18 04:27:24 UTC |
committer | Brad Fitzpatrick
<bradfitz@golang.org> 2016-05-18 15:10:36 UTC |
parent | 76e74a336561c500a376b406eb649727eeb3477f |
http2/go17.go | +44 | -0 |
http2/not_go17.go | +8 | -0 |
http2/transport.go | +19 | -0 |
diff --git a/http2/go17.go b/http2/go17.go index 5f6238c..2e2eabd 100644 --- a/http2/go17.go +++ b/http2/go17.go @@ -9,8 +9,52 @@ package http2 import ( "context" "net/http" + "net/http/httptrace" + "time" ) +type clientTrace httptrace.ClientTrace + func reqContext(r *http.Request) context.Context { return r.Context() } func setResponseUncompressed(res *http.Response) { res.Uncompressed = true } + +func traceGotConn(req *http.Request, cc *ClientConn) { + trace := httptrace.ContextClientTrace(req.Context()) + if trace == nil || trace.GotConn == nil { + return + } + ci := httptrace.GotConnInfo{Conn: cc.tconn} + cc.mu.Lock() + ci.Reused = cc.nextStreamID > 1 + ci.WasIdle = len(cc.streams) == 0 + if ci.WasIdle { + ci.IdleTime = time.Now().Sub(cc.lastActive) + } + cc.mu.Unlock() + + trace.GotConn(ci) +} + +func traceWroteHeaders(trace *clientTrace) { + if trace != nil && trace.WroteHeaders != nil { + trace.WroteHeaders() + } +} + +func traceWroteRequest(trace *clientTrace, err error) { + if trace != nil && trace.WroteRequest != nil { + trace.WroteRequest(httptrace.WroteRequestInfo{Err: err}) + } +} + +func traceFirstResponseByte(trace *clientTrace) { + if trace != nil && trace.GotFirstResponseByte != nil { + trace.GotFirstResponseByte() + } +} + +func requestTrace(req *http.Request) *clientTrace { + trace := httptrace.ContextClientTrace(req.Context()) + return (*clientTrace)(trace) +} diff --git a/http2/not_go17.go b/http2/not_go17.go index c7a4d30..deffe68 100644 --- a/http2/not_go17.go +++ b/http2/not_go17.go @@ -20,3 +20,11 @@ func reqContext(r *http.Request) fakeContext { func setResponseUncompressed(res *http.Response) { // Nothing. } + +type clientTrace struct{} + +func requestTrace(*http.Request) *clientTrace { return nil } +func traceGotConn(*http.Request, *ClientConn) {} +func traceFirstResponseByte(*clientTrace) {} +func traceWroteHeaders(*clientTrace) {} +func traceWroteRequest(*clientTrace, error) {} diff --git a/http2/transport.go b/http2/transport.go index a7ada8f..4aa3b7b 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -153,6 +153,8 @@ type ClientConn struct { bw *bufio.Writer br *bufio.Reader fr *Framer + lastActive time.Time + // Settings from peer: maxFrameSize uint32 maxConcurrentStreams uint32 @@ -170,6 +172,7 @@ type ClientConn struct { type clientStream struct { cc *ClientConn req *http.Request + trace *clientTrace // or nil ID uint32 resc chan resAndError bufPipe pipe // buffered pipe with the flow-controlled response payload @@ -288,6 +291,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) return nil, err } + traceGotConn(req, cc) res, err := cc.RoundTrip(req) if shouldRetryRequest(req, err) { continue @@ -622,6 +626,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { } cc.mu.Lock() + cc.lastActive = time.Now() if cc.closed || !cc.canTakeNewRequestLocked() { cc.mu.Unlock() return nil, errClientConnUnusable @@ -629,6 +634,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { cs := cc.newStream() cs.req = req + cs.trace = requestTrace(req) hasBody := body != nil // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? @@ -659,6 +665,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { endStream := !hasBody && !hasTrailers werr := cc.writeHeaders(cs.ID, endStream, hdrs) cc.wmu.Unlock() + traceWroteHeaders(cs.trace) cc.mu.Unlock() if werr != nil { @@ -668,6 +675,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { cc.forgetStreamID(cs.ID) // Don't bother sending a RST_STREAM (our write already failed; // no need to keep writing) + traceWroteRequest(cs.trace, werr) return nil, werr } @@ -679,6 +687,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { bodyCopyErrc <- cs.writeRequestBody(body, req.Body) }() } else { + traceWroteRequest(cs.trace, nil) if d := cc.responseHeaderTimeout(); d != 0 { timer := time.NewTimer(d) defer timer.Stop() @@ -743,6 +752,7 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { // forgetStreamID. return nil, cs.resetErr case err := <-bodyCopyErrc: + traceWroteRequest(cs.trace, err) if err != nil { return nil, err } @@ -1068,6 +1078,7 @@ func (cc *ClientConn) streamByID(id uint32, andRemove bool) *clientStream { defer cc.mu.Unlock() cs := cc.streams[id] if andRemove && cs != nil && !cc.closed { + cc.lastActive = time.Now() delete(cc.streams, id) close(cs.done) } @@ -1196,6 +1207,13 @@ func (rl *clientConnReadLoop) processHeaders(f *MetaHeadersFrame) error { } else { return rl.processTrailers(cs, f) } + if cs.trace != nil { + // TODO(bradfitz): move first response byte earlier, + // when we first read the 9 byte header, not waiting + // until all the HEADERS+CONTINUATION frames have been + // merged. This works for now. + traceFirstResponseByte(cs.trace) + } res, err := rl.handleResponse(cs, f) if err != nil { @@ -1243,6 +1261,7 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra if statusCode == 100 { // Just skip 100-continue response headers for now. // TODO: golang.org/issue/13851 for doing it properly. + // TODO: also call the httptrace.ClientTrace hooks cs.pastHeaders = false // do it all again return nil, nil }