},
}.run(t)
}
+
+// Tests that closing the Request.Cancel channel also while still
+// reading the response body. Issue 13159.
+func TestCancelRequestMidBody_h1(t *testing.T) { testCancelRequestMidBody(t, h1Mode) }
+func TestCancelRequestMidBody_h2(t *testing.T) { testCancelRequestMidBody(t, h2Mode) }
+func testCancelRequestMidBody(t *testing.T, h2 bool) {
+ defer afterTest(t)
+ unblock := make(chan bool)
+ didFlush := make(chan bool, 1)
+ cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
+ io.WriteString(w, "Hello")
+ w.(Flusher).Flush()
+ didFlush <- true
+ <-unblock
+ io.WriteString(w, ", world.")
+ <-unblock
+ }))
+ defer cst.close()
+ defer close(unblock)
+
+ req, _ := NewRequest("GET", cst.ts.URL, nil)
+ cancel := make(chan struct{})
+ req.Cancel = cancel
+
+ res, err := cst.c.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ <-didFlush
+ close(cancel)
+
+ slurp, err := ioutil.ReadAll(res.Body)
+ if string(slurp) != "Hello" {
+ t.Errorf("Read %q; want Hello", slurp)
+ }
+ if !reflect.DeepEqual(err, ExportErrRequestCanceled) {
+ t.Errorf("ReadAll error = %v; want %v", err, ExportErrRequestCanceled)
+ }
+}
// io.Pipe except there are no PipeReader/PipeWriter halves, and the
// underlying buffer is an interface. (io.Pipe is always unbuffered)
type http2pipe struct {
- mu sync.Mutex
- c sync.Cond // c.L must point to
- b http2pipeBuffer
- err error // read error once empty. non-nil means closed.
+ mu sync.Mutex
+ c sync.Cond // c.L must point to
+ b http2pipeBuffer
+ err error // read error once empty. non-nil means closed.
+ donec chan struct{} // closed on error
}
type http2pipeBuffer interface {
defer p.c.Signal()
if p.err == nil {
p.err = err
+ if p.donec != nil {
+ close(p.donec)
+ }
}
}
return p.err
}
+// Done returns a channel which is closed if and when this pipe is closed
+// with CloseWithError.
+func (p *http2pipe) Done() <-chan struct{} {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ if p.donec == nil {
+ p.donec = make(chan struct{})
+ if p.err != nil {
+
+ close(p.donec)
+ }
+ }
+ return p.donec
+}
+
const (
http2prefaceTimeout = 10 * time.Second
http2firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
resetErr error // populated before peerReset is closed
}
+// awaitRequestCancel runs in its own goroutine and waits for the user's
+func (cs *http2clientStream) awaitRequestCancel(cancel <-chan struct{}) {
+ if cancel == nil {
+ return
+ }
+ select {
+ case <-cancel:
+ cs.bufPipe.CloseWithError(http2errRequestCanceled)
+ case <-cs.bufPipe.Done():
+ }
+}
+
// checkReset reports any error sent in a RST_STREAM frame by the
// server.
func (cs *http2clientStream) checkReset() error {
}
+// errRequestCanceled is a copy of net/http's errRequestCanceled because it's not
+// exported. At least they'll be DeepEqual for h1-vs-h2 comparisons tests.
+var http2errRequestCanceled = errors.New("net/http: request canceled")
+
func (cc *http2ClientConn) RoundTrip(req *Request) (*Response, error) {
cc.mu.Lock()
cc.fr.WriteContinuation(cs.ID, endHeaders, chunk)
}
}
+
cc.bw.Flush()
werr := cc.werr
cc.wmu.Unlock()
res.Request = req
res.TLS = cc.tlsState
return res, nil
+ case <-req.Cancel:
+ cs.abortRequestBodyWrite()
+ return nil, http2errRequestCanceled
case err := <-bodyCopyErrc:
if err != nil {
return nil, err
cs.bufPipe = http2pipe{b: buf}
cs.bytesRemain = res.ContentLength
res.Body = http2transportResponseBody{cs}
+ go cs.awaitRequestCancel(cs.req.Cancel)
if cs.requestedGzip && res.Header.Get("Content-Encoding") == "gzip" {
res.Header.Del("Content-Encoding")