}
func TestConcurrentReadWriteReqBody_h1(t *testing.T) { testConcurrentReadWriteReqBody(t, h1Mode) }
-func TestConcurrentReadWriteReqBody_h2(t *testing.T) {
- t.Skip("known failing; golang.org/issue/13659")
- testConcurrentReadWriteReqBody(t, h2Mode)
-}
+func TestConcurrentReadWriteReqBody_h2(t *testing.T) { testConcurrentReadWriteReqBody(t, h2Mode) }
func testConcurrentReadWriteReqBody(t *testing.T, h2 bool) {
defer afterTest(t)
const reqBody = "some request body"
// our HTTP/1 implementation intentionally
// doesn't permit writes during read (mostly
// due to it being undefined); if that is ever
- // relaxed, fix this.
+ // relaxed, change this.
<-didRead
}
io.WriteString(w, resBody)
return &http2ContinuationFrame{fh, p}, nil
}
-func (f *http2ContinuationFrame) StreamEnded() bool {
- return f.http2FrameHeader.Flags.Has(http2FlagDataEndStream)
-}
-
func (f *http2ContinuationFrame) HeaderBlockFragment() []byte {
f.checkValid()
return f.headerFragBuf
done chan struct{} // closed when stream remove from cc.streams map; close calls guarded by cc.mu
// owned by clientConnReadLoop:
- headersDone bool // got HEADERS w/ END_HEADERS
- trailersDone bool // got second HEADERS frame w/ END_HEADERS
+ pastHeaders bool // got HEADERS w/ END_HEADERS
+ pastTrailers bool // got second HEADERS frame w/ END_HEADERS
trailer Header // accumulated trailers
resTrailer Header // client's Response.Trailer
hdec *hpack.Decoder
// Fields reset on each HEADERS:
- nextRes *Response
- sawRegHeader bool // saw non-pseudo header
- reqMalformed error // non-nil once known to be malformed
+ nextRes *Response
+ sawRegHeader bool // saw non-pseudo header
+ reqMalformed error // non-nil once known to be malformed
+ lastHeaderEndsStream bool
}
// readLoop runs in its own goroutine and reads and dispatches frames.
func (rl *http2clientConnReadLoop) processHeaders(f *http2HeadersFrame) error {
rl.sawRegHeader = false
rl.reqMalformed = nil
+ rl.lastHeaderEndsStream = f.StreamEnded()
rl.nextRes = &Response{
Proto: "HTTP/2.0",
ProtoMajor: 2,
Header: make(Header),
}
- return rl.processHeaderBlockFragment(f.HeaderBlockFragment(), f.StreamID, f.HeadersEnded(), f.StreamEnded())
+ return rl.processHeaderBlockFragment(f.HeaderBlockFragment(), f.StreamID, f.HeadersEnded())
}
func (rl *http2clientConnReadLoop) processContinuation(f *http2ContinuationFrame) error {
- return rl.processHeaderBlockFragment(f.HeaderBlockFragment(), f.StreamID, f.HeadersEnded(), f.StreamEnded())
+ return rl.processHeaderBlockFragment(f.HeaderBlockFragment(), f.StreamID, f.HeadersEnded())
}
-func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, streamID uint32, headersEnded, streamEnded bool) error {
+func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, streamID uint32, finalFrag bool) error {
cc := rl.cc
- cs := cc.streamByID(streamID, streamEnded)
+ streamEnded := rl.lastHeaderEndsStream
+ cs := cc.streamByID(streamID, streamEnded && finalFrag)
if cs == nil {
return nil
}
- if cs.headersDone {
+ if cs.pastHeaders {
rl.hdec.SetEmitFunc(cs.onNewTrailerField)
} else {
rl.hdec.SetEmitFunc(rl.onNewHeaderField)
if err != nil {
return http2ConnectionError(http2ErrCodeCompression)
}
- if err := rl.hdec.Close(); err != nil {
- return http2ConnectionError(http2ErrCodeCompression)
+ if finalFrag {
+ if err := rl.hdec.Close(); err != nil {
+ return http2ConnectionError(http2ErrCodeCompression)
+ }
}
- if !headersEnded {
+
+ if !finalFrag {
return nil
}
- if !cs.headersDone {
- cs.headersDone = true
+ if !cs.pastHeaders {
+ cs.pastHeaders = true
} else {
- if cs.trailersDone {
+ if cs.pastTrailers {
return http2ConnectionError(http2ErrCodeProtocol)
}
- cs.trailersDone = true
+ cs.pastTrailers = true
if !streamEnded {
return http2ConnectionError(http2ErrCodeProtocol)
res := rl.nextRes
+ if res.StatusCode == 100 {
+
+ cs.pastHeaders = false
+ return nil
+ }
+
if !streamEnded || cs.req.Method == "HEAD" {
res.ContentLength = -1
if clens := res.Header["Content-Length"]; len(clens) == 1 {