summaryrefslogtreecommitdiff
path: root/src/pkg/net/http/transport_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/net/http/transport_test.go')
-rw-r--r--src/pkg/net/http/transport_test.go529
1 files changed, 506 insertions, 23 deletions
diff --git a/src/pkg/net/http/transport_test.go b/src/pkg/net/http/transport_test.go
index e4df30a98..964ca0fca 100644
--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -11,9 +11,12 @@ import (
"bytes"
"compress/gzip"
"crypto/rand"
+ "crypto/tls"
+ "errors"
"fmt"
"io"
"io/ioutil"
+ "log"
"net"
"net/http"
. "net/http"
@@ -54,21 +57,21 @@ func (c *testCloseConn) Close() error {
// been closed.
type testConnSet struct {
t *testing.T
+ mu sync.Mutex // guards closed and list
closed map[net.Conn]bool
list []net.Conn // in order created
- mutex sync.Mutex
}
func (tcs *testConnSet) insert(c net.Conn) {
- tcs.mutex.Lock()
- defer tcs.mutex.Unlock()
+ tcs.mu.Lock()
+ defer tcs.mu.Unlock()
tcs.closed[c] = false
tcs.list = append(tcs.list, c)
}
func (tcs *testConnSet) remove(c net.Conn) {
- tcs.mutex.Lock()
- defer tcs.mutex.Unlock()
+ tcs.mu.Lock()
+ defer tcs.mu.Unlock()
tcs.closed[c] = true
}
@@ -91,11 +94,19 @@ func makeTestDial(t *testing.T) (*testConnSet, func(n, addr string) (net.Conn, e
}
func (tcs *testConnSet) check(t *testing.T) {
- tcs.mutex.Lock()
- defer tcs.mutex.Unlock()
-
- for i, c := range tcs.list {
- if !tcs.closed[c] {
+ tcs.mu.Lock()
+ defer tcs.mu.Unlock()
+ for i := 4; i >= 0; i-- {
+ for i, c := range tcs.list {
+ if tcs.closed[c] {
+ continue
+ }
+ if i != 0 {
+ tcs.mu.Unlock()
+ time.Sleep(50 * time.Millisecond)
+ tcs.mu.Lock()
+ continue
+ }
t.Errorf("TCP connection #%d, %p (of %d total) was not closed", i+1, c, len(tcs.list))
}
}
@@ -271,6 +282,58 @@ func TestTransportIdleCacheKeys(t *testing.T) {
}
}
+// Tests that the HTTP transport re-uses connections when a client
+// reads to the end of a response Body without closing it.
+func TestTransportReadToEndReusesConn(t *testing.T) {
+ defer afterTest(t)
+ const msg = "foobar"
+
+ var addrSeen map[string]int
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ addrSeen[r.RemoteAddr]++
+ if r.URL.Path == "/chunked/" {
+ w.WriteHeader(200)
+ w.(http.Flusher).Flush()
+ } else {
+ w.Header().Set("Content-Type", strconv.Itoa(len(msg)))
+ w.WriteHeader(200)
+ }
+ w.Write([]byte(msg))
+ }))
+ defer ts.Close()
+
+ buf := make([]byte, len(msg))
+
+ for pi, path := range []string{"/content-length/", "/chunked/"} {
+ wantLen := []int{len(msg), -1}[pi]
+ addrSeen = make(map[string]int)
+ for i := 0; i < 3; i++ {
+ res, err := http.Get(ts.URL + path)
+ if err != nil {
+ t.Errorf("Get %s: %v", path, err)
+ continue
+ }
+ // We want to close this body eventually (before the
+ // defer afterTest at top runs), but not before the
+ // len(addrSeen) check at the bottom of this test,
+ // since Closing this early in the loop would risk
+ // making connections be re-used for the wrong reason.
+ defer res.Body.Close()
+
+ if res.ContentLength != int64(wantLen) {
+ t.Errorf("%s res.ContentLength = %d; want %d", path, res.ContentLength, wantLen)
+ }
+ n, err := res.Body.Read(buf)
+ if n != len(msg) || err != io.EOF {
+ t.Errorf("%s Read = %v, %v; want %d, EOF", path, n, err, len(msg))
+ }
+ }
+ if len(addrSeen) != 1 {
+ t.Errorf("for %s, server saw %d distinct client addresses; want 1", path, len(addrSeen))
+ }
+ }
+}
+
func TestTransportMaxPerHostIdleConns(t *testing.T) {
defer afterTest(t)
resch := make(chan string)
@@ -295,10 +358,11 @@ func TestTransportMaxPerHostIdleConns(t *testing.T) {
resp, err := c.Get(ts.URL)
if err != nil {
t.Error(err)
+ return
}
- _, err = ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatalf("ReadAll: %v", err)
+ if _, err := ioutil.ReadAll(resp.Body); err != nil {
+ t.Errorf("ReadAll: %v", err)
+ return
}
donech <- true
}
@@ -739,8 +803,38 @@ func TestTransportGzipRecursive(t *testing.T) {
}
}
+// golang.org/issue/7750: request fails when server replies with
+// a short gzip body
+func TestTransportGzipShort(t *testing.T) {
+ defer afterTest(t)
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ w.Header().Set("Content-Encoding", "gzip")
+ w.Write([]byte{0x1f, 0x8b})
+ }))
+ defer ts.Close()
+
+ tr := &Transport{}
+ defer tr.CloseIdleConnections()
+ c := &Client{Transport: tr}
+ res, err := c.Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ _, err = ioutil.ReadAll(res.Body)
+ if err == nil {
+ t.Fatal("Expect an error from reading a body.")
+ }
+ if err != io.ErrUnexpectedEOF {
+ t.Errorf("ReadAll error = %v; want io.ErrUnexpectedEOF", err)
+ }
+}
+
// tests that persistent goroutine connections shut down when no longer desired.
func TestTransportPersistConnLeak(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping test; see http://golang.org/issue/7237")
+ }
defer afterTest(t)
gotReqCh := make(chan bool)
unblockCh := make(chan bool)
@@ -798,8 +892,8 @@ func TestTransportPersistConnLeak(t *testing.T) {
// We expect 0 or 1 extra goroutine, empirically. Allow up to 5.
// Previously we were leaking one per numReq.
- t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
if int(growth) > 5 {
+ t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
t.Error("too many new goroutines")
}
}
@@ -807,6 +901,9 @@ func TestTransportPersistConnLeak(t *testing.T) {
// golang.org/issue/4531: Transport leaks goroutines when
// request.ContentLength is explicitly short
func TestTransportPersistConnLeakShortBody(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping test; see http://golang.org/issue/7237")
+ }
defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
}))
@@ -1014,6 +1111,9 @@ func TestTransportConcurrency(t *testing.T) {
}
func TestIssue4191_InfiniteGetTimeout(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping test; see http://golang.org/issue/7237")
+ }
defer afterTest(t)
const debug = false
mux := NewServeMux()
@@ -1075,6 +1175,9 @@ func TestIssue4191_InfiniteGetTimeout(t *testing.T) {
}
func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping test; see http://golang.org/issue/7237")
+ }
defer afterTest(t)
const debug = false
mux := NewServeMux()
@@ -1147,9 +1250,13 @@ func TestTransportResponseHeaderTimeout(t *testing.T) {
if testing.Short() {
t.Skip("skipping timeout test in -short mode")
}
+ inHandler := make(chan bool, 1)
mux := NewServeMux()
- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {})
+ mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {
+ inHandler <- true
+ })
mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {
+ inHandler <- true
time.Sleep(2 * time.Second)
})
ts := httptest.NewServer(mux)
@@ -1172,7 +1279,27 @@ func TestTransportResponseHeaderTimeout(t *testing.T) {
}
for i, tt := range tests {
res, err := c.Get(ts.URL + tt.path)
+ select {
+ case <-inHandler:
+ case <-time.After(5 * time.Second):
+ t.Errorf("never entered handler for test index %d, %s", i, tt.path)
+ continue
+ }
if err != nil {
+ uerr, ok := err.(*url.Error)
+ if !ok {
+ t.Errorf("error is not an url.Error; got: %#v", err)
+ continue
+ }
+ nerr, ok := uerr.Err.(net.Error)
+ if !ok {
+ t.Errorf("error does not satisfy net.Error interface; got: %#v", err)
+ continue
+ }
+ if !nerr.Timeout() {
+ t.Errorf("want timeout error; got: %q", nerr)
+ continue
+ }
if strings.Contains(err.Error(), tt.wantErr) {
continue
}
@@ -1243,6 +1370,60 @@ func TestTransportCancelRequest(t *testing.T) {
}
}
+func TestTransportCancelRequestInDial(t *testing.T) {
+ defer afterTest(t)
+ if testing.Short() {
+ t.Skip("skipping test in -short mode")
+ }
+ var logbuf bytes.Buffer
+ eventLog := log.New(&logbuf, "", 0)
+
+ unblockDial := make(chan bool)
+ defer close(unblockDial)
+
+ inDial := make(chan bool)
+ tr := &Transport{
+ Dial: func(network, addr string) (net.Conn, error) {
+ eventLog.Println("dial: blocking")
+ inDial <- true
+ <-unblockDial
+ return nil, errors.New("nope")
+ },
+ }
+ cl := &Client{Transport: tr}
+ gotres := make(chan bool)
+ req, _ := NewRequest("GET", "http://something.no-network.tld/", nil)
+ go func() {
+ _, err := cl.Do(req)
+ eventLog.Printf("Get = %v", err)
+ gotres <- true
+ }()
+
+ select {
+ case <-inDial:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout; never saw blocking dial")
+ }
+
+ eventLog.Printf("canceling")
+ tr.CancelRequest(req)
+
+ select {
+ case <-gotres:
+ case <-time.After(5 * time.Second):
+ panic("hang. events are: " + logbuf.String())
+ }
+
+ got := logbuf.String()
+ want := `dial: blocking
+canceling
+Get = Get http://something.no-network.tld/: net/http: request canceled while waiting for connection
+`
+ if got != want {
+ t.Errorf("Got events:\n%s\nWant:\n%s", got, want)
+ }
+}
+
// golang.org/issue/3672 -- Client can't close HTTP stream
// Calling Close on a Response.Body used to just read until EOF.
// Now it actually closes the TCP connection.
@@ -1283,7 +1464,7 @@ func TestTransportCloseResponseBody(t *testing.T) {
t.Fatal(err)
}
if !bytes.Equal(buf, want) {
- t.Errorf("read %q; want %q", buf, want)
+ t.Fatalf("read %q; want %q", buf, want)
}
didClose := make(chan error, 1)
go func() {
@@ -1372,8 +1553,10 @@ func TestTransportSocketLateBinding(t *testing.T) {
dialGate := make(chan bool, 1)
tr := &Transport{
Dial: func(n, addr string) (net.Conn, error) {
- <-dialGate
- return net.Dial(n, addr)
+ if <-dialGate {
+ return net.Dial(n, addr)
+ }
+ return nil, errors.New("manually closed")
},
DisableKeepAlives: false,
}
@@ -1408,7 +1591,7 @@ func TestTransportSocketLateBinding(t *testing.T) {
t.Fatalf("/foo came from conn %q; /bar came from %q instead", fooAddr, barAddr)
}
barRes.Body.Close()
- dialGate <- true
+ dialGate <- false
}
// Issue 2184
@@ -1559,13 +1742,11 @@ var proxyFromEnvTests = []proxyFromEnvTest{
}
func TestProxyFromEnvironment(t *testing.T) {
- os.Setenv("HTTP_PROXY", "")
- os.Setenv("http_proxy", "")
- os.Setenv("NO_PROXY", "")
- os.Setenv("no_proxy", "")
+ ResetProxyEnv()
for _, tt := range proxyFromEnvTests {
os.Setenv("HTTP_PROXY", tt.env)
os.Setenv("NO_PROXY", tt.noenv)
+ ResetCachedEnvironment()
reqURL := tt.req
if reqURL == "" {
reqURL = "http://example.com"
@@ -1643,6 +1824,308 @@ func TestTransportClosesRequestBody(t *testing.T) {
}
}
+func TestTransportTLSHandshakeTimeout(t *testing.T) {
+ defer afterTest(t)
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ ln := newLocalListener(t)
+ defer ln.Close()
+ testdonec := make(chan struct{})
+ defer close(testdonec)
+
+ go func() {
+ c, err := ln.Accept()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ <-testdonec
+ c.Close()
+ }()
+
+ getdonec := make(chan struct{})
+ go func() {
+ defer close(getdonec)
+ tr := &Transport{
+ Dial: func(_, _ string) (net.Conn, error) {
+ return net.Dial("tcp", ln.Addr().String())
+ },
+ TLSHandshakeTimeout: 250 * time.Millisecond,
+ }
+ cl := &Client{Transport: tr}
+ _, err := cl.Get("https://dummy.tld/")
+ if err == nil {
+ t.Error("expected error")
+ return
+ }
+ ue, ok := err.(*url.Error)
+ if !ok {
+ t.Errorf("expected url.Error; got %#v", err)
+ return
+ }
+ ne, ok := ue.Err.(net.Error)
+ if !ok {
+ t.Errorf("expected net.Error; got %#v", err)
+ return
+ }
+ if !ne.Timeout() {
+ t.Errorf("expected timeout error; got %v", err)
+ }
+ if !strings.Contains(err.Error(), "handshake timeout") {
+ t.Errorf("expected 'handshake timeout' in error; got %v", err)
+ }
+ }()
+ select {
+ case <-getdonec:
+ case <-time.After(5 * time.Second):
+ t.Error("test timeout; TLS handshake hung?")
+ }
+}
+
+// Trying to repro golang.org/issue/3514
+func TestTLSServerClosesConnection(t *testing.T) {
+ defer afterTest(t)
+ if runtime.GOOS == "windows" {
+ t.Skip("skipping flaky test on Windows; golang.org/issue/7634")
+ }
+ closedc := make(chan bool, 1)
+ ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ if strings.Contains(r.URL.Path, "/keep-alive-then-die") {
+ conn, _, _ := w.(Hijacker).Hijack()
+ conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo"))
+ conn.Close()
+ closedc <- true
+ return
+ }
+ fmt.Fprintf(w, "hello")
+ }))
+ defer ts.Close()
+ tr := &Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+ defer tr.CloseIdleConnections()
+ client := &Client{Transport: tr}
+
+ var nSuccess = 0
+ var errs []error
+ const trials = 20
+ for i := 0; i < trials; i++ {
+ tr.CloseIdleConnections()
+ res, err := client.Get(ts.URL + "/keep-alive-then-die")
+ if err != nil {
+ t.Fatal(err)
+ }
+ <-closedc
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(slurp) != "foo" {
+ t.Errorf("Got %q, want foo", slurp)
+ }
+
+ // Now try again and see if we successfully
+ // pick a new connection.
+ res, err = client.Get(ts.URL + "/")
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ slurp, err = ioutil.ReadAll(res.Body)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+ nSuccess++
+ }
+ if nSuccess > 0 {
+ t.Logf("successes = %d of %d", nSuccess, trials)
+ } else {
+ t.Errorf("All runs failed:")
+ }
+ for _, err := range errs {
+ t.Logf(" err: %v", err)
+ }
+}
+
+// byteFromChanReader is an io.Reader that reads a single byte at a
+// time from the channel. When the channel is closed, the reader
+// returns io.EOF.
+type byteFromChanReader chan byte
+
+func (c byteFromChanReader) Read(p []byte) (n int, err error) {
+ if len(p) == 0 {
+ return
+ }
+ b, ok := <-c
+ if !ok {
+ return 0, io.EOF
+ }
+ p[0] = b
+ return 1, nil
+}
+
+// Verifies that the Transport doesn't reuse a connection in the case
+// where the server replies before the request has been fully
+// written. We still honor that reply (see TestIssue3595), but don't
+// send future requests on the connection because it's then in a
+// questionable state.
+// golang.org/issue/7569
+func TestTransportNoReuseAfterEarlyResponse(t *testing.T) {
+ defer afterTest(t)
+ var sconn struct {
+ sync.Mutex
+ c net.Conn
+ }
+ var getOkay bool
+ closeConn := func() {
+ sconn.Lock()
+ defer sconn.Unlock()
+ if sconn.c != nil {
+ sconn.c.Close()
+ sconn.c = nil
+ if !getOkay {
+ t.Logf("Closed server connection")
+ }
+ }
+ }
+ defer closeConn()
+
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ if r.Method == "GET" {
+ io.WriteString(w, "bar")
+ return
+ }
+ conn, _, _ := w.(Hijacker).Hijack()
+ sconn.Lock()
+ sconn.c = conn
+ sconn.Unlock()
+ conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo")) // keep-alive
+ go io.Copy(ioutil.Discard, conn)
+ }))
+ defer ts.Close()
+ tr := &Transport{}
+ defer tr.CloseIdleConnections()
+ client := &Client{Transport: tr}
+
+ const bodySize = 256 << 10
+ finalBit := make(byteFromChanReader, 1)
+ req, _ := NewRequest("POST", ts.URL, io.MultiReader(io.LimitReader(neverEnding('x'), bodySize-1), finalBit))
+ req.ContentLength = bodySize
+ res, err := client.Do(req)
+ if err := wantBody(res, err, "foo"); err != nil {
+ t.Errorf("POST response: %v", err)
+ }
+ donec := make(chan bool)
+ go func() {
+ defer close(donec)
+ res, err = client.Get(ts.URL)
+ if err := wantBody(res, err, "bar"); err != nil {
+ t.Errorf("GET response: %v", err)
+ return
+ }
+ getOkay = true // suppress test noise
+ }()
+ time.AfterFunc(5*time.Second, closeConn)
+ select {
+ case <-donec:
+ finalBit <- 'x' // unblock the writeloop of the first Post
+ close(finalBit)
+ case <-time.After(7 * time.Second):
+ t.Fatal("timeout waiting for GET request to finish")
+ }
+}
+
+type errorReader struct {
+ err error
+}
+
+func (e errorReader) Read(p []byte) (int, error) { return 0, e.err }
+
+type closerFunc func() error
+
+func (f closerFunc) Close() error { return f() }
+
+// Issue 6981
+func TestTransportClosesBodyOnError(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("skipping test; see http://golang.org/issue/7782")
+ }
+ defer afterTest(t)
+ readBody := make(chan error, 1)
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ _, err := ioutil.ReadAll(r.Body)
+ readBody <- err
+ }))
+ defer ts.Close()
+ fakeErr := errors.New("fake error")
+ didClose := make(chan bool, 1)
+ req, _ := NewRequest("POST", ts.URL, struct {
+ io.Reader
+ io.Closer
+ }{
+ io.MultiReader(io.LimitReader(neverEnding('x'), 1<<20), errorReader{fakeErr}),
+ closerFunc(func() error {
+ select {
+ case didClose <- true:
+ default:
+ }
+ return nil
+ }),
+ })
+ res, err := DefaultClient.Do(req)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err == nil || !strings.Contains(err.Error(), fakeErr.Error()) {
+ t.Fatalf("Do error = %v; want something containing %q", err, fakeErr.Error())
+ }
+ select {
+ case err := <-readBody:
+ if err == nil {
+ t.Errorf("Unexpected success reading request body from handler; want 'unexpected EOF reading trailer'")
+ }
+ case <-time.After(5 * time.Second):
+ t.Error("timeout waiting for server handler to complete")
+ }
+ select {
+ case <-didClose:
+ default:
+ t.Errorf("didn't see Body.Close")
+ }
+}
+
+func wantBody(res *http.Response, err error, want string) error {
+ if err != nil {
+ return err
+ }
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return fmt.Errorf("error reading body: %v", err)
+ }
+ if string(slurp) != want {
+ return fmt.Errorf("body = %q; want %q", slurp, want)
+ }
+ if err := res.Body.Close(); err != nil {
+ return fmt.Errorf("body Close = %v", err)
+ }
+ return nil
+}
+
+func newLocalListener(t *testing.T) net.Listener {
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ ln, err = net.Listen("tcp6", "[::1]:0")
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ return ln
+}
+
type countCloseReader struct {
n *int
io.Reader