Commit 77c041cc authored by Tom Bergan's avatar Tom Bergan

net/http: update bundled http2

Updates http2 to x/net/http2 git rev 1087133bc4a for:

  http2: reject DATA frame before HEADERS frame
  https://golang.org/cl/56770

  http2: respect peer's SETTINGS_MAX_HEADER_LIST_SIZE in ClientConn
  https://golang.org/cl/29243

  http2: reset client stream after processing response headers
  https://golang.org/cl/70510

Also updated TestRequestLimit_h2 as the behavior changed slightly due
to https://golang.org/cl/29243.

Fixes #13959
Fixes #20521
Fixes #21466

Change-Id: Iac659694f3a48b8bd485546a4f96a932e3056026
Reviewed-on: https://go-review.googlesource.com/71611
Run-TryBot: Tom Bergan <tombergan@google.com>
Reviewed-by: default avatarJoe Tsai <thebrokentoaster@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 48754592
...@@ -6606,7 +6606,7 @@ type http2Transport struct { ...@@ -6606,7 +6606,7 @@ type http2Transport struct {
// MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to // MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
// send in the initial settings frame. It is how many bytes // send in the initial settings frame. It is how many bytes
// of response headers are allow. Unlike the http2 spec, zero here // of response headers are allowed. Unlike the http2 spec, zero here
// means to use a default limit (currently 10MB). If you actually // means to use a default limit (currently 10MB). If you actually
// want to advertise an ulimited value to the peer, Transport // want to advertise an ulimited value to the peer, Transport
// interprets the highest possible value here (0xffffffff or 1<<32-1) // interprets the highest possible value here (0xffffffff or 1<<32-1)
...@@ -6693,6 +6693,7 @@ type http2ClientConn struct { ...@@ -6693,6 +6693,7 @@ type http2ClientConn struct {
// Settings from peer: (also guarded by mu) // Settings from peer: (also guarded by mu)
maxFrameSize uint32 maxFrameSize uint32
maxConcurrentStreams uint32 maxConcurrentStreams uint32
peerMaxHeaderListSize uint64
initialWindowSize uint32 initialWindowSize uint32
hbuf bytes.Buffer // HPACK encoder writes into this hbuf bytes.Buffer // HPACK encoder writes into this
...@@ -7045,6 +7046,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client ...@@ -7045,6 +7046,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client
maxFrameSize: 16 << 10, // spec default maxFrameSize: 16 << 10, // spec default
initialWindowSize: 65535, // spec default initialWindowSize: 65535, // spec default
maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough. maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead.
streams: make(map[uint32]*http2clientStream), streams: make(map[uint32]*http2clientStream),
singleUse: singleUse, singleUse: singleUse,
wantSettingsAck: true, wantSettingsAck: true,
...@@ -7604,8 +7606,13 @@ func (cs *http2clientStream) writeRequestBody(body io.Reader, bodyCloser io.Clos ...@@ -7604,8 +7606,13 @@ func (cs *http2clientStream) writeRequestBody(body io.Reader, bodyCloser io.Clos
var trls []byte var trls []byte
if hasTrailers { if hasTrailers {
cc.mu.Lock() cc.mu.Lock()
defer cc.mu.Unlock() trls, err = cc.encodeTrailers(req)
trls = cc.encodeTrailers(req) cc.mu.Unlock()
if err != nil {
cc.writeStreamReset(cs.ID, http2ErrCodeInternal, err)
cc.forgetStreamID(cs.ID)
return err
}
} }
cc.wmu.Lock() cc.wmu.Lock()
...@@ -7708,36 +7715,37 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail ...@@ -7708,36 +7715,37 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail
} }
} }
enumerateHeaders := func(f func(name, value string)) {
// 8.1.2.3 Request Pseudo-Header Fields // 8.1.2.3 Request Pseudo-Header Fields
// The :path pseudo-header field includes the path and query parts of the // The :path pseudo-header field includes the path and query parts of the
// target URI (the path-absolute production and optionally a '?' character // target URI (the path-absolute production and optionally a '?' character
// followed by the query production (see Sections 3.3 and 3.4 of // followed by the query production (see Sections 3.3 and 3.4 of
// [RFC3986]). // [RFC3986]).
cc.writeHeader(":authority", host) f(":authority", host)
cc.writeHeader(":method", req.Method) f(":method", req.Method)
if req.Method != "CONNECT" { if req.Method != "CONNECT" {
cc.writeHeader(":path", path) f(":path", path)
cc.writeHeader(":scheme", req.URL.Scheme) f(":scheme", req.URL.Scheme)
} }
if trailers != "" { if trailers != "" {
cc.writeHeader("trailer", trailers) f("trailer", trailers)
} }
var didUA bool var didUA bool
for k, vv := range req.Header { for k, vv := range req.Header {
lowKey := strings.ToLower(k) if strings.EqualFold(k, "host") || strings.EqualFold(k, "content-length") {
switch lowKey {
case "host", "content-length":
// Host is :authority, already sent. // Host is :authority, already sent.
// Content-Length is automatic, set below. // Content-Length is automatic, set below.
continue continue
case "connection", "proxy-connection", "transfer-encoding", "upgrade", "keep-alive": } else if strings.EqualFold(k, "connection") || strings.EqualFold(k, "proxy-connection") ||
strings.EqualFold(k, "transfer-encoding") || strings.EqualFold(k, "upgrade") ||
strings.EqualFold(k, "keep-alive") {
// Per 8.1.2.2 Connection-Specific Header // Per 8.1.2.2 Connection-Specific Header
// Fields, don't send connection-specific // Fields, don't send connection-specific
// fields. We have already checked if any // fields. We have already checked if any
// are error-worthy so just ignore the rest. // are error-worthy so just ignore the rest.
continue continue
case "user-agent": } else if strings.EqualFold(k, "user-agent") {
// Match Go's http1 behavior: at most one // Match Go's http1 behavior: at most one
// User-Agent. If set to nil or empty string, // User-Agent. If set to nil or empty string,
// then omit it. Otherwise if not mentioned, // then omit it. Otherwise if not mentioned,
...@@ -7750,20 +7758,43 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail ...@@ -7750,20 +7758,43 @@ func (cc *http2ClientConn) encodeHeaders(req *Request, addGzipHeader bool, trail
if vv[0] == "" { if vv[0] == "" {
continue continue
} }
} }
for _, v := range vv { for _, v := range vv {
cc.writeHeader(lowKey, v) f(k, v)
} }
} }
if http2shouldSendReqContentLength(req.Method, contentLength) { if http2shouldSendReqContentLength(req.Method, contentLength) {
cc.writeHeader("content-length", strconv.FormatInt(contentLength, 10)) f("content-length", strconv.FormatInt(contentLength, 10))
} }
if addGzipHeader { if addGzipHeader {
cc.writeHeader("accept-encoding", "gzip") f("accept-encoding", "gzip")
} }
if !didUA { if !didUA {
cc.writeHeader("user-agent", http2defaultUserAgent) f("user-agent", http2defaultUserAgent)
}
} }
// Do a first pass over the headers counting bytes to ensure
// we don't exceed cc.peerMaxHeaderListSize. This is done as a
// separate pass before encoding the headers to prevent
// modifying the hpack state.
hlSize := uint64(0)
enumerateHeaders(func(name, value string) {
hf := hpack.HeaderField{Name: name, Value: value}
hlSize += uint64(hf.Size())
})
if hlSize > cc.peerMaxHeaderListSize {
return nil, http2errRequestHeaderListSize
}
// Header list size is ok. Write the headers.
enumerateHeaders(func(name, value string) {
cc.writeHeader(strings.ToLower(name), value)
})
return cc.hbuf.Bytes(), nil return cc.hbuf.Bytes(), nil
} }
...@@ -7790,17 +7821,29 @@ func http2shouldSendReqContentLength(method string, contentLength int64) bool { ...@@ -7790,17 +7821,29 @@ func http2shouldSendReqContentLength(method string, contentLength int64) bool {
} }
// requires cc.mu be held. // requires cc.mu be held.
func (cc *http2ClientConn) encodeTrailers(req *Request) []byte { func (cc *http2ClientConn) encodeTrailers(req *Request) ([]byte, error) {
cc.hbuf.Reset() cc.hbuf.Reset()
hlSize := uint64(0)
for k, vv := range req.Trailer { for k, vv := range req.Trailer {
// Transfer-Encoding, etc.. have already been filter at the for _, v := range vv {
hf := hpack.HeaderField{Name: k, Value: v}
hlSize += uint64(hf.Size())
}
}
if hlSize > cc.peerMaxHeaderListSize {
return nil, http2errRequestHeaderListSize
}
for k, vv := range req.Trailer {
// Transfer-Encoding, etc.. have already been filtered at the
// start of RoundTrip // start of RoundTrip
lowKey := strings.ToLower(k) lowKey := strings.ToLower(k)
for _, v := range vv { for _, v := range vv {
cc.writeHeader(lowKey, v) cc.writeHeader(lowKey, v)
} }
} }
return cc.hbuf.Bytes() return cc.hbuf.Bytes(), nil
} }
func (cc *http2ClientConn) writeHeader(name, value string) { func (cc *http2ClientConn) writeHeader(name, value string) {
...@@ -8012,7 +8055,17 @@ func (rl *http2clientConnReadLoop) run() error { ...@@ -8012,7 +8055,17 @@ func (rl *http2clientConnReadLoop) run() error {
func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) error { func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) error {
cc := rl.cc cc := rl.cc
cs := cc.streamByID(f.StreamID, f.StreamEnded()) if f.StreamEnded() {
// Issue 20521: If the stream has ended, streamByID() causes
// clientStream.done to be closed, which causes the request's bodyWriter
// to be closed with an errStreamClosed, which may be received by
// clientConn.RoundTrip before the result of processing these headers.
// Deferring stream closure allows the header processing to occur first.
// clientConn.RoundTrip may still receive the bodyWriter error first, but
// the fix for issue 16102 prioritises any response.
defer cc.streamByID(f.StreamID, true)
}
cs := cc.streamByID(f.StreamID, false)
if cs == nil { if cs == nil {
// We'd get here if we canceled a request while the // We'd get here if we canceled a request while the
// server had its response still in flight. So if this // server had its response still in flight. So if this
...@@ -8308,6 +8361,14 @@ func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error { ...@@ -8308,6 +8361,14 @@ func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error {
} }
return nil return nil
} }
if !cs.firstByte {
cc.logf("protocol error: received DATA before a HEADERS frame")
rl.endStreamError(cs, http2StreamError{
StreamID: f.StreamID,
Code: http2ErrCodeProtocol,
})
return nil
}
if f.Length > 0 { if f.Length > 0 {
// Check connection-level flow control. // Check connection-level flow control.
cc.mu.Lock() cc.mu.Lock()
...@@ -8422,6 +8483,8 @@ func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error ...@@ -8422,6 +8483,8 @@ func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error
cc.maxFrameSize = s.Val cc.maxFrameSize = s.Val
case http2SettingMaxConcurrentStreams: case http2SettingMaxConcurrentStreams:
cc.maxConcurrentStreams = s.Val cc.maxConcurrentStreams = s.Val
case http2SettingMaxHeaderListSize:
cc.peerMaxHeaderListSize = uint64(s.Val)
case http2SettingInitialWindowSize: case http2SettingInitialWindowSize:
// Values above the maximum flow-control // Values above the maximum flow-control
// window size of 2^31-1 MUST be treated as a // window size of 2^31-1 MUST be treated as a
...@@ -8588,6 +8651,7 @@ func (cc *http2ClientConn) writeStreamReset(streamID uint32, code http2ErrCode, ...@@ -8588,6 +8651,7 @@ func (cc *http2ClientConn) writeStreamReset(streamID uint32, code http2ErrCode,
var ( var (
http2errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") http2errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
http2errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit")
http2errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers") http2errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
) )
......
...@@ -2765,16 +2765,29 @@ func testRequestLimit(t *testing.T, h2 bool) { ...@@ -2765,16 +2765,29 @@ func testRequestLimit(t *testing.T, h2 bool) {
req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i)) req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i))
} }
res, err := cst.c.Do(req) res, err := cst.c.Do(req)
if err != nil { if res != nil {
defer res.Body.Close()
}
if h2 {
// In HTTP/2, the result depends on a race. If the client has received the
// server's SETTINGS before RoundTrip starts sending the request, then RoundTrip
// will fail with an error. Otherwise, the client should receive a 431 from the
// server.
if err == nil && res.StatusCode != 431 {
t.Fatalf("expected 431 response status; got: %d %s", res.StatusCode, res.Status)
}
} else {
// In HTTP/1, we expect a 431 from the server.
// Some HTTP clients may fail on this undefined behavior (server replying and // Some HTTP clients may fail on this undefined behavior (server replying and
// closing the connection while the request is still being written), but // closing the connection while the request is still being written), but
// we do support it (at least currently), so we expect a response below. // we do support it (at least currently), so we expect a response below.
if err != nil {
t.Fatalf("Do: %v", err) t.Fatalf("Do: %v", err)
} }
defer res.Body.Close()
if res.StatusCode != 431 { if res.StatusCode != 431 {
t.Fatalf("expected 431 response status; got: %d %s", res.StatusCode, res.Status) t.Fatalf("expected 431 response status; got: %d %s", res.StatusCode, res.Status)
} }
}
} }
type neverEnding byte type neverEnding byte
......
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