// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build !plan9,!go1.7 package ctxhttp import ( "net" "net/http" "net/http/httptest" "sync" "testing" "time" "golang.org/x/net/context" ) // golang.org/issue/14065 func TestClosesResponseBodyOnCancel(t *testing.T) { defer func() { testHookContextDoneBeforeHeaders = nop }() defer func() { testHookDoReturned = nop }() defer func() { testHookDidBodyClose = nop }() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) defer ts.Close() ctx, cancel := context.WithCancel(context.Background()) // closed when Do enters select case <-ctx.Done() enteredDonePath := make(chan struct{}) testHookContextDoneBeforeHeaders = func() { close(enteredDonePath) } testHookDoReturned = func() { // We now have the result (the Flush'd headers) at least, // so we can cancel the request. cancel() // But block the client.Do goroutine from sending // until Do enters into the <-ctx.Done() path, since // otherwise if both channels are readable, select // picks a random one. <-enteredDonePath } sawBodyClose := make(chan struct{}) testHookDidBodyClose = func() { close(sawBodyClose) } tr := &http.Transport{} defer tr.CloseIdleConnections() c := &http.Client{Transport: tr} req, _ := http.NewRequest("GET", ts.URL, nil) _, doErr := Do(ctx, c, req) select { case <-sawBodyClose: case <-time.After(5 * time.Second): t.Fatal("timeout waiting for body to close") } if doErr != ctx.Err() { t.Errorf("Do error = %v; want %v", doErr, ctx.Err()) } } type noteCloseConn struct { net.Conn onceClose sync.Once closefn func() } func (c *noteCloseConn) Close() error { c.onceClose.Do(c.closefn) return c.Conn.Close() }