Commit a50fbcd3 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

net/http: update bundled http2

Updates http2 to x/net/http2 git rev 40a0a18 for:

    http2: fix Server race with concurrent Read/Close
    http2: make Server reuse 64k request body buffer between requests
    http2: never Read from Request.Body in Transport to determine ContentLength

Fixes #17480
Updates #17071

Change-Id: If142925764a2e148f95957f559637cfc1785ad21
Reviewed-on: https://go-review.googlesource.com/31737Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 40d4be59
...@@ -2384,6 +2384,7 @@ var ( ...@@ -2384,6 +2384,7 @@ var (
http2VerboseLogs bool http2VerboseLogs bool
http2logFrameWrites bool http2logFrameWrites bool
http2logFrameReads bool http2logFrameReads bool
http2inTests bool
) )
func init() { func init() {
...@@ -3153,7 +3154,6 @@ type http2serverConn struct { ...@@ -3153,7 +3154,6 @@ type http2serverConn struct {
goAwayCode http2ErrCode goAwayCode http2ErrCode
shutdownTimerCh <-chan time.Time // nil until used shutdownTimerCh <-chan time.Time // nil until used
shutdownTimer *time.Timer // nil until used shutdownTimer *time.Timer // nil until used
freeRequestBodyBuf []byte // if non-nil, a free initialWindowSize buffer for getRequestBodyBuf
// Owned by the writeFrameAsync goroutine: // Owned by the writeFrameAsync goroutine:
headerWriteBuf bytes.Buffer headerWriteBuf bytes.Buffer
...@@ -3197,11 +3197,11 @@ type http2stream struct { ...@@ -3197,11 +3197,11 @@ type http2stream struct {
numTrailerValues int64 numTrailerValues int64
weight uint8 weight uint8
state http2streamState state http2streamState
sentReset bool // only true once detached from streams map sentReset bool // only true once detached from streams map
gotReset bool // only true once detacted from streams map gotReset bool // only true once detacted from streams map
gotTrailerHeader bool // HEADER frame for trailers was seen gotTrailerHeader bool // HEADER frame for trailers was seen
wroteHeaders bool // whether we wrote headers (not status 100) wroteHeaders bool // whether we wrote headers (not status 100)
reqBuf []byte reqBuf []byte // if non-nil, body pipe buffer to return later at EOF
trailer Header // accumulated trailers trailer Header // accumulated trailers
reqTrailer Header // handler's Request.Trailer reqTrailer Header // handler's Request.Trailer
...@@ -3890,10 +3890,6 @@ func (sc *http2serverConn) closeStream(st *http2stream, err error) { ...@@ -3890,10 +3890,6 @@ func (sc *http2serverConn) closeStream(st *http2stream, err error) {
} }
st.cw.Close() st.cw.Close()
sc.writeSched.forgetStream(st.id) sc.writeSched.forgetStream(st.id)
if st.reqBuf != nil {
sc.freeRequestBodyBuf = st.reqBuf
}
} }
func (sc *http2serverConn) processSettings(f *http2SettingsFrame) error { func (sc *http2serverConn) processSettings(f *http2SettingsFrame) error {
...@@ -4287,11 +4283,9 @@ func (sc *http2serverConn) newWriterAndRequest(st *http2stream, f *http2MetaHead ...@@ -4287,11 +4283,9 @@ func (sc *http2serverConn) newWriterAndRequest(st *http2stream, f *http2MetaHead
} }
req = http2requestWithContext(req, st.ctx) req = http2requestWithContext(req, st.ctx)
if bodyOpen { if bodyOpen {
st.reqBuf = http2getRequestBodyBuf()
buf := make([]byte, http2initialWindowSize)
body.pipe = &http2pipe{ body.pipe = &http2pipe{
b: &http2fixedBuffer{buf: buf}, b: &http2fixedBuffer{buf: st.reqBuf},
} }
if vv, ok := header["Content-Length"]; ok { if vv, ok := header["Content-Length"]; ok {
...@@ -4315,13 +4309,22 @@ func (sc *http2serverConn) newWriterAndRequest(st *http2stream, f *http2MetaHead ...@@ -4315,13 +4309,22 @@ func (sc *http2serverConn) newWriterAndRequest(st *http2stream, f *http2MetaHead
return rw, req, nil return rw, req, nil
} }
func (sc *http2serverConn) getRequestBodyBuf() []byte { var http2reqBodyCache = make(chan []byte, 8)
sc.serveG.check()
if buf := sc.freeRequestBodyBuf; buf != nil { func http2getRequestBodyBuf() []byte {
sc.freeRequestBodyBuf = nil select {
return buf case b := <-http2reqBodyCache:
return b
default:
return make([]byte, http2initialWindowSize)
}
}
func http2putRequestBodyBuf(b []byte) {
select {
case http2reqBodyCache <- b:
default:
} }
return make([]byte, http2initialWindowSize)
} }
// Run on its own goroutine. // Run on its own goroutine.
...@@ -4406,11 +4409,19 @@ type http2bodyReadMsg struct { ...@@ -4406,11 +4409,19 @@ type http2bodyReadMsg struct {
// called from handler goroutines. // called from handler goroutines.
// Notes that the handler for the given stream ID read n bytes of its body // Notes that the handler for the given stream ID read n bytes of its body
// and schedules flow control tokens to be sent. // and schedules flow control tokens to be sent.
func (sc *http2serverConn) noteBodyReadFromHandler(st *http2stream, n int) { func (sc *http2serverConn) noteBodyReadFromHandler(st *http2stream, n int, err error) {
sc.serveG.checkNotOn() sc.serveG.checkNotOn()
select { if n > 0 {
case sc.bodyReadCh <- http2bodyReadMsg{st, n}: select {
case <-sc.doneServing: case sc.bodyReadCh <- http2bodyReadMsg{st, n}:
case <-sc.doneServing:
}
}
if err == io.EOF {
if buf := st.reqBuf; buf != nil {
st.reqBuf = nil
http2putRequestBodyBuf(buf)
}
} }
} }
...@@ -4467,16 +4478,19 @@ func (sc *http2serverConn) sendWindowUpdate32(st *http2stream, n int32) { ...@@ -4467,16 +4478,19 @@ func (sc *http2serverConn) sendWindowUpdate32(st *http2stream, n int32) {
} }
} }
// requestBody is the Handler's Request.Body type.
// Read and Close may be called concurrently.
type http2requestBody struct { type http2requestBody struct {
stream *http2stream stream *http2stream
conn *http2serverConn conn *http2serverConn
closed bool closed bool // for use by Close only
sawEOF bool // for use by Read only
pipe *http2pipe // non-nil if we have a HTTP entity message body pipe *http2pipe // non-nil if we have a HTTP entity message body
needsContinue bool // need to send a 100-continue needsContinue bool // need to send a 100-continue
} }
func (b *http2requestBody) Close() error { func (b *http2requestBody) Close() error {
if b.pipe != nil { if b.pipe != nil && !b.closed {
b.pipe.BreakWithError(http2errClosedBody) b.pipe.BreakWithError(http2errClosedBody)
} }
b.closed = true b.closed = true
...@@ -4488,13 +4502,17 @@ func (b *http2requestBody) Read(p []byte) (n int, err error) { ...@@ -4488,13 +4502,17 @@ func (b *http2requestBody) Read(p []byte) (n int, err error) {
b.needsContinue = false b.needsContinue = false
b.conn.write100ContinueHeaders(b.stream) b.conn.write100ContinueHeaders(b.stream)
} }
if b.pipe == nil { if b.pipe == nil || b.sawEOF {
return 0, io.EOF return 0, io.EOF
} }
n, err = b.pipe.Read(p) n, err = b.pipe.Read(p)
if n > 0 { if err == io.EOF {
b.conn.noteBodyReadFromHandler(b.stream, n) b.sawEOF = true
}
if b.conn == nil && http2inTests {
return
} }
b.conn.noteBodyReadFromHandler(b.stream, n, err)
return return
} }
...@@ -5493,46 +5511,28 @@ func (cc *http2ClientConn) responseHeaderTimeout() time.Duration { ...@@ -5493,46 +5511,28 @@ func (cc *http2ClientConn) responseHeaderTimeout() time.Duration {
// Certain headers are special-cased as okay but not transmitted later. // Certain headers are special-cased as okay but not transmitted later.
func http2checkConnHeaders(req *Request) error { func http2checkConnHeaders(req *Request) error {
if v := req.Header.Get("Upgrade"); v != "" { if v := req.Header.Get("Upgrade"); v != "" {
return errors.New("http2: invalid Upgrade request header") return fmt.Errorf("http2: invalid Upgrade request header: %q", req.Header["Upgrade"])
} }
if v := req.Header.Get("Transfer-Encoding"); (v != "" && v != "chunked") || len(req.Header["Transfer-Encoding"]) > 1 { if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
return errors.New("http2: invalid Transfer-Encoding request header") return fmt.Errorf("http2: invalid Transfer-Encoding request header: %q", vv)
} }
if v := req.Header.Get("Connection"); (v != "" && v != "close" && v != "keep-alive") || len(req.Header["Connection"]) > 1 { if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "close" && vv[0] != "keep-alive") {
return errors.New("http2: invalid Connection request header") return fmt.Errorf("http2: invalid Connection request header: %q", vv)
} }
return nil return nil
} }
func http2bodyAndLength(req *Request) (body io.Reader, contentLen int64) { // actualContentLength returns a sanitized version of
body = req.Body // req.ContentLength, where 0 actually means zero (not unknown) and -1
if body == nil { // means unknown.
return nil, 0 func http2actualContentLength(req *Request) int64 {
if req.Body == nil {
return 0
} }
if req.ContentLength != 0 { if req.ContentLength != 0 {
return req.Body, req.ContentLength return req.ContentLength
}
if req.Header.Get("Expect") == "100-continue" {
return req.Body, -1
}
// We have a body but a zero content length. Test to see if
// it's actually zero or just unset.
var buf [1]byte
n, rerr := body.Read(buf[:])
if rerr != nil && rerr != io.EOF {
return http2errorReader{rerr}, -1
} }
if n == 1 { return -1
if rerr == io.EOF {
return bytes.NewReader(buf[:]), 1
}
return io.MultiReader(bytes.NewReader(buf[:]), body), -1
}
return nil, 0
} }
func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
...@@ -5556,8 +5556,9 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) { ...@@ -5556,8 +5556,9 @@ func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
return nil, http2errClientConnUnusable return nil, http2errClientConnUnusable
} }
body, contentLen := http2bodyAndLength(req) body := req.Body
hasBody := body != nil hasBody := body != nil
contentLen := http2actualContentLength(req)
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere? // TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
var requestedGzip bool var requestedGzip bool
......
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