"io/ioutil"
"log"
"net"
+ "net/http/httptrace"
"net/textproto"
"net/url"
"os"
return buf.String()
}
+type http2clientTrace httptrace.ClientTrace
+
func http2reqContext(r *Request) context.Context { return r.Context() }
func http2setResponseUncompressed(res *Response) { res.Uncompressed = true }
+func http2traceGotConn(req *Request, cc *http2ClientConn) {
+ 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 http2traceWroteHeaders(trace *http2clientTrace) {
+ if trace != nil && trace.WroteHeaders != nil {
+ trace.WroteHeaders()
+ }
+}
+
+func http2traceWroteRequest(trace *http2clientTrace, err error) {
+ if trace != nil && trace.WroteRequest != nil {
+ trace.WroteRequest(httptrace.WroteRequestInfo{Err: err})
+ }
+}
+
+func http2traceFirstResponseByte(trace *http2clientTrace) {
+ if trace != nil && trace.GotFirstResponseByte != nil {
+ trace.GotFirstResponseByte()
+ }
+}
+
+func http2requestTrace(req *Request) *http2clientTrace {
+ trace := httptrace.ContextClientTrace(req.Context())
+ return (*http2clientTrace)(trace)
+}
+
var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
type http2goroutineLock uint64
bw *bufio.Writer
br *bufio.Reader
fr *http2Framer
+ lastActive time.Time
+
// Settings from peer:
maxFrameSize uint32
maxConcurrentStreams uint32
type http2clientStream struct {
cc *http2ClientConn
req *Request
+ trace *http2clientTrace // or nil
ID uint32
resc chan http2resAndError
bufPipe http2pipe // buffered pipe with the flow-controlled response payload
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
return nil, err
}
+ http2traceGotConn(req, cc)
res, err := cc.RoundTrip(req)
if http2shouldRetryRequest(req, err) {
continue
}
cc.mu.Lock()
+ cc.lastActive = time.Now()
if cc.closed || !cc.canTakeNewRequestLocked() {
cc.mu.Unlock()
return nil, http2errClientConnUnusable
cs := cc.newStream()
cs.req = req
+ cs.trace = http2requestTrace(req)
hasBody := body != nil
if !cc.t.disableCompression() &&
endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, hdrs)
cc.wmu.Unlock()
+ http2traceWroteHeaders(cs.trace)
cc.mu.Unlock()
if werr != nil {
}
cc.forgetStreamID(cs.ID)
+ http2traceWroteRequest(cs.trace, werr)
return nil, werr
}
bodyCopyErrc <- cs.writeRequestBody(body, req.Body)
}()
} else {
+ http2traceWroteRequest(cs.trace, nil)
if d := cc.responseHeaderTimeout(); d != 0 {
timer := time.NewTimer(d)
defer timer.Stop()
return nil, cs.resetErr
case err := <-bodyCopyErrc:
+ http2traceWroteRequest(cs.trace, err)
if err != nil {
return nil, err
}
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)
}
} else {
return rl.processTrailers(cs, f)
}
+ if cs.trace != nil {
+
+ http2traceFirstResponseByte(cs.trace)
+ }
res, err := rl.handleResponse(cs, f)
if err != nil {
}
}
-func TestTransportEventTrace(t *testing.T) { testTransportEventTrace(t, false) }
+func TestTransportEventTrace(t *testing.T) { testTransportEventTrace(t, h1Mode, false) }
+func TestTransportEventTrace_h2(t *testing.T) { testTransportEventTrace(t, h2Mode, false) }
// test a non-nil httptrace.ClientTrace but with all hooks set to zero.
-func TestTransportEventTrace_NoHooks(t *testing.T) { testTransportEventTrace(t, true) }
+func TestTransportEventTrace_NoHooks(t *testing.T) { testTransportEventTrace(t, h1Mode, true) }
+func TestTransportEventTrace_NoHooks_h2(t *testing.T) { testTransportEventTrace(t, h2Mode, true) }
-func testTransportEventTrace(t *testing.T, noHooks bool) {
+func testTransportEventTrace(t *testing.T, h2 bool, noHooks bool) {
defer afterTest(t)
const resBody = "some body"
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
if _, err := ioutil.ReadAll(r.Body); err != nil {
t.Error(err)
}
io.WriteString(w, resBody)
}))
- defer ts.Close()
- tr := &Transport{
- ExpectContinueTimeout: 1 * time.Second,
+ defer cst.close()
+ if !h2 {
+ cst.tr.ExpectContinueTimeout = 1 * time.Second
}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
var mu sync.Mutex
var buf bytes.Buffer
buf.WriteByte('\n')
}
- ip, port, err := net.SplitHostPort(ts.Listener.Addr().String())
+ addrStr := cst.ts.Listener.Addr().String()
+ ip, port, err := net.SplitHostPort(addrStr)
if err != nil {
t.Fatal(err)
}
return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil
})
- req, _ := NewRequest("POST", "http://dns-is-faked.golang:"+port, strings.NewReader("some body"))
+ req, _ := NewRequest("POST", cst.scheme()+"://dns-is-faked.golang:"+port, strings.NewReader("some body"))
trace := &httptrace.ClientTrace{
GetConn: func(hostPort string) { logf("Getting conn for %v ...", hostPort) },
GotConn: func(ci httptrace.GotConnInfo) { logf("got conn: %+v", ci) },
req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
req.Header.Set("Expect", "100-continue")
- res, err := c.Do(req)
+ res, err := cst.c.Do(req)
if err != nil {
t.Fatal(err)
}
wantSub("Getting conn for dns-is-faked.golang:" + port)
wantSub("DNS start: {Host:dns-is-faked.golang}")
wantSub("DNS done: {Addrs:[{IP:" + ip + " Zone:}] Err:<nil> Coalesced:false}")
- wantSub("Connecting to tcp " + ts.Listener.Addr().String())
- wantSub("connected to tcp " + ts.Listener.Addr().String() + " = <nil>")
+ wantSub("Connecting to tcp " + addrStr)
+ wantSub("connected to tcp " + addrStr + " = <nil>")
wantSub("Reused:false WasIdle:false IdleTime:0s")
wantSub("first response byte")
- wantSub("PutIdleConn = <nil>")
+ if !h2 {
+ wantSub("PutIdleConn = <nil>")
+ // TODO: implement these next two for Issue 13851
+ wantSub("Wait100Continue")
+ wantSub("Got100Continue")
+ }
wantSub("WroteRequest: {Err:<nil>}")
- wantSub("Wait100Continue")
- wantSub("Got100Continue")
if strings.Contains(got, " to udp ") {
t.Errorf("should not see UDP (DNS) connections")
}