Commit 1119af89 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: update bundled x/net/http2 for httptrace changes

Updates x/net/http2 to 3b99394 for golang.org/cl/23205

And associated tests.

Fixes #12580

Change-Id: I1f4b59267b453d241f2afaa315b7fe10d477e52d
Reviewed-on: https://go-review.googlesource.com/23206Reviewed-by: 's avatarAndrew Gerrand <adg@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 4d2ac544
...@@ -44,6 +44,13 @@ func (t *clientServerTest) close() { ...@@ -44,6 +44,13 @@ func (t *clientServerTest) close() {
t.ts.Close() t.ts.Close()
} }
func (t *clientServerTest) scheme() string {
if t.h2 {
return "https"
}
return "http"
}
const ( const (
h1Mode = false h1Mode = false
h2Mode = true h2Mode = true
......
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/http/httptrace"
"net/textproto" "net/textproto"
"net/url" "net/url"
"os" "os"
...@@ -1973,10 +1974,52 @@ func http2summarizeFrame(f http2Frame) string { ...@@ -1973,10 +1974,52 @@ func http2summarizeFrame(f http2Frame) string {
return buf.String() return buf.String()
} }
type http2clientTrace httptrace.ClientTrace
func http2reqContext(r *Request) context.Context { return r.Context() } func http2reqContext(r *Request) context.Context { return r.Context() }
func http2setResponseUncompressed(res *Response) { res.Uncompressed = true } 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" var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
type http2goroutineLock uint64 type http2goroutineLock uint64
...@@ -4879,6 +4922,8 @@ type http2ClientConn struct { ...@@ -4879,6 +4922,8 @@ type http2ClientConn struct {
bw *bufio.Writer bw *bufio.Writer
br *bufio.Reader br *bufio.Reader
fr *http2Framer fr *http2Framer
lastActive time.Time
// Settings from peer: // Settings from peer:
maxFrameSize uint32 maxFrameSize uint32
maxConcurrentStreams uint32 maxConcurrentStreams uint32
...@@ -4896,6 +4941,7 @@ type http2ClientConn struct { ...@@ -4896,6 +4941,7 @@ type http2ClientConn struct {
type http2clientStream struct { type http2clientStream struct {
cc *http2ClientConn cc *http2ClientConn
req *Request req *Request
trace *http2clientTrace // or nil
ID uint32 ID uint32
resc chan http2resAndError resc chan http2resAndError
bufPipe http2pipe // buffered pipe with the flow-controlled response payload bufPipe http2pipe // buffered pipe with the flow-controlled response payload
...@@ -5014,6 +5060,7 @@ func (t *http2Transport) RoundTripOpt(req *Request, opt http2RoundTripOpt) (*Res ...@@ -5014,6 +5060,7 @@ func (t *http2Transport) RoundTripOpt(req *Request, opt http2RoundTripOpt) (*Res
t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
return nil, err return nil, err
} }
http2traceGotConn(req, cc)
res, err := cc.RoundTrip(req) res, err := cc.RoundTrip(req)
if http2shouldRetryRequest(req, err) { if http2shouldRetryRequest(req, err) {
continue continue
...@@ -5335,6 +5382,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5335,6 +5382,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
} }
cc.mu.Lock() cc.mu.Lock()
cc.lastActive = time.Now()
if cc.closed || !cc.canTakeNewRequestLocked() { if cc.closed || !cc.canTakeNewRequestLocked() {
cc.mu.Unlock() cc.mu.Unlock()
return nil, http2errClientConnUnusable return nil, http2errClientConnUnusable
...@@ -5342,6 +5390,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5342,6 +5390,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
cs := cc.newStream() cs := cc.newStream()
cs.req = req cs.req = req
cs.trace = http2requestTrace(req)
hasBody := body != nil hasBody := body != nil
if !cc.t.disableCompression() && if !cc.t.disableCompression() &&
...@@ -5357,6 +5406,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5357,6 +5406,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
endStream := !hasBody && !hasTrailers endStream := !hasBody && !hasTrailers
werr := cc.writeHeaders(cs.ID, endStream, hdrs) werr := cc.writeHeaders(cs.ID, endStream, hdrs)
cc.wmu.Unlock() cc.wmu.Unlock()
http2traceWroteHeaders(cs.trace)
cc.mu.Unlock() cc.mu.Unlock()
if werr != nil { if werr != nil {
...@@ -5365,6 +5415,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5365,6 +5415,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
} }
cc.forgetStreamID(cs.ID) cc.forgetStreamID(cs.ID)
http2traceWroteRequest(cs.trace, werr)
return nil, werr return nil, werr
} }
...@@ -5376,6 +5427,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5376,6 +5427,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
bodyCopyErrc <- cs.writeRequestBody(body, req.Body) bodyCopyErrc <- cs.writeRequestBody(body, req.Body)
}() }()
} else { } else {
http2traceWroteRequest(cs.trace, nil)
if d := cc.responseHeaderTimeout(); d != 0 { if d := cc.responseHeaderTimeout(); d != 0 {
timer := time.NewTimer(d) timer := time.NewTimer(d)
defer timer.Stop() defer timer.Stop()
...@@ -5430,6 +5482,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5430,6 +5482,7 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
return nil, cs.resetErr return nil, cs.resetErr
case err := <-bodyCopyErrc: case err := <-bodyCopyErrc:
http2traceWroteRequest(cs.trace, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -5729,6 +5782,7 @@ func (cc *http2ClientConn) streamByID(id uint32, andRemove bool) *http2clientStr ...@@ -5729,6 +5782,7 @@ func (cc *http2ClientConn) streamByID(id uint32, andRemove bool) *http2clientStr
defer cc.mu.Unlock() defer cc.mu.Unlock()
cs := cc.streams[id] cs := cc.streams[id]
if andRemove && cs != nil && !cc.closed { if andRemove && cs != nil && !cc.closed {
cc.lastActive = time.Now()
delete(cc.streams, id) delete(cc.streams, id)
close(cs.done) close(cs.done)
} }
...@@ -5852,6 +5906,10 @@ func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) erro ...@@ -5852,6 +5906,10 @@ func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) erro
} else { } else {
return rl.processTrailers(cs, f) return rl.processTrailers(cs, f)
} }
if cs.trace != nil {
http2traceFirstResponseByte(cs.trace)
}
res, err := rl.handleResponse(cs, f) res, err := rl.handleResponse(cs, f)
if err != nil { if err != nil {
......
...@@ -90,6 +90,7 @@ type ClientTrace struct { ...@@ -90,6 +90,7 @@ type ClientTrace struct {
// connection reuse is disabled via Transport.DisableKeepAlives. // connection reuse is disabled via Transport.DisableKeepAlives.
// PutIdleConn is called before the caller's Response.Body.Close // PutIdleConn is called before the caller's Response.Body.Close
// call returns. // call returns.
// For HTTP/2, this hook is not currently used.
PutIdleConn func(err error) PutIdleConn func(err error)
// GotFirstResponseByte is called when the first byte of the response // GotFirstResponseByte is called when the first byte of the response
......
...@@ -3193,26 +3193,26 @@ func TestTransportResponseHeaderLength(t *testing.T) { ...@@ -3193,26 +3193,26 @@ func TestTransportResponseHeaderLength(t *testing.T) {
} }
} }
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. // 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) defer afterTest(t)
const resBody = "some body" 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 { if _, err := ioutil.ReadAll(r.Body); err != nil {
t.Error(err) t.Error(err)
} }
io.WriteString(w, resBody) io.WriteString(w, resBody)
})) }))
defer ts.Close() defer cst.close()
tr := &Transport{ if !h2 {
ExpectContinueTimeout: 1 * time.Second, cst.tr.ExpectContinueTimeout = 1 * time.Second
} }
defer tr.CloseIdleConnections()
c := &Client{Transport: tr}
var mu sync.Mutex var mu sync.Mutex
var buf bytes.Buffer var buf bytes.Buffer
...@@ -3223,7 +3223,8 @@ func testTransportEventTrace(t *testing.T, noHooks bool) { ...@@ -3223,7 +3223,8 @@ func testTransportEventTrace(t *testing.T, noHooks bool) {
buf.WriteByte('\n') 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -3237,7 +3238,7 @@ func testTransportEventTrace(t *testing.T, noHooks bool) { ...@@ -3237,7 +3238,7 @@ func testTransportEventTrace(t *testing.T, noHooks bool) {
return []net.IPAddr{{IP: net.ParseIP(ip)}}, nil 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{ trace := &httptrace.ClientTrace{
GetConn: func(hostPort string) { logf("Getting conn for %v ...", hostPort) }, GetConn: func(hostPort string) { logf("Getting conn for %v ...", hostPort) },
GotConn: func(ci httptrace.GotConnInfo) { logf("got conn: %+v", ci) }, GotConn: func(ci httptrace.GotConnInfo) { logf("got conn: %+v", ci) },
...@@ -3263,7 +3264,7 @@ func testTransportEventTrace(t *testing.T, noHooks bool) { ...@@ -3263,7 +3264,7 @@ func testTransportEventTrace(t *testing.T, noHooks bool) {
req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
req.Header.Set("Expect", "100-continue") req.Header.Set("Expect", "100-continue")
res, err := c.Do(req) res, err := cst.c.Do(req)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -3292,14 +3293,17 @@ func testTransportEventTrace(t *testing.T, noHooks bool) { ...@@ -3292,14 +3293,17 @@ func testTransportEventTrace(t *testing.T, noHooks bool) {
wantSub("Getting conn for dns-is-faked.golang:" + port) wantSub("Getting conn for dns-is-faked.golang:" + port)
wantSub("DNS start: {Host:dns-is-faked.golang}") wantSub("DNS start: {Host:dns-is-faked.golang}")
wantSub("DNS done: {Addrs:[{IP:" + ip + " Zone:}] Err:<nil> Coalesced:false}") wantSub("DNS done: {Addrs:[{IP:" + ip + " Zone:}] Err:<nil> Coalesced:false}")
wantSub("Connecting to tcp " + ts.Listener.Addr().String()) wantSub("Connecting to tcp " + addrStr)
wantSub("connected to tcp " + ts.Listener.Addr().String() + " = <nil>") wantSub("connected to tcp " + addrStr + " = <nil>")
wantSub("Reused:false WasIdle:false IdleTime:0s") wantSub("Reused:false WasIdle:false IdleTime:0s")
wantSub("first response byte") 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("WroteRequest: {Err:<nil>}")
wantSub("Wait100Continue")
wantSub("Got100Continue")
if strings.Contains(got, " to udp ") { if strings.Contains(got, " to udp ") {
t.Errorf("should not see UDP (DNS) connections") t.Errorf("should not see UDP (DNS) connections")
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment