diff options
Diffstat (limited to 'libgo/go/net/http/transport_test.go')
-rw-r--r-- | libgo/go/net/http/transport_test.go | 415 |
1 files changed, 356 insertions, 59 deletions
diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index c21d4afa87f..3b2a5f978e2 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Tests for transport.go +// Tests for transport.go. +// +// More tests are in clientserver_test.go (for things testing both client & server for both +// HTTP/1 and HTTP/2). This package http_test @@ -20,6 +23,8 @@ import ( "net" . "net/http" "net/http/httptest" + "net/http/httputil" + "net/http/internal" "net/url" "os" "reflect" @@ -256,6 +261,7 @@ func TestTransportConnectionCloseOnRequest(t *testing.T) { // if the Transport's DisableKeepAlives is set, all requests should // send Connection: close. +// HTTP/1-only (Connection: close doesn't exist in h2) func TestTransportConnectionCloseOnRequestDisableKeepAlive(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) @@ -431,6 +437,7 @@ func TestTransportMaxPerHostIdleConns(t *testing.T) { } func TestTransportServerClosingUnexpectedly(t *testing.T) { + setParallel(t) defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() @@ -597,6 +604,7 @@ func TestTransportHeadChunkedResponse(t *testing.T) { tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} + defer tr.CloseIdleConnections() // Ensure that we wait for the readLoop to complete before // calling Head again @@ -790,6 +798,94 @@ func TestTransportGzip(t *testing.T) { } } +// If a request has Expect:100-continue header, the request blocks sending body until the first response. +// Premature consumption of the request body should not be occurred. +func TestTransportExpect100Continue(t *testing.T) { + defer afterTest(t) + + ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + switch req.URL.Path { + case "/100": + // This endpoint implicitly responds 100 Continue and reads body. + if _, err := io.Copy(ioutil.Discard, req.Body); err != nil { + t.Error("Failed to read Body", err) + } + rw.WriteHeader(StatusOK) + case "/200": + // Go 1.5 adds Connection: close header if the client expect + // continue but not entire request body is consumed. + rw.WriteHeader(StatusOK) + case "/500": + rw.WriteHeader(StatusInternalServerError) + case "/keepalive": + // This hijacked endpoint responds error without Connection:close. + _, bufrw, err := rw.(Hijacker).Hijack() + if err != nil { + log.Fatal(err) + } + bufrw.WriteString("HTTP/1.1 500 Internal Server Error\r\n") + bufrw.WriteString("Content-Length: 0\r\n\r\n") + bufrw.Flush() + case "/timeout": + // This endpoint tries to read body without 100 (Continue) response. + // After ExpectContinueTimeout, the reading will be started. + conn, bufrw, err := rw.(Hijacker).Hijack() + if err != nil { + log.Fatal(err) + } + if _, err := io.CopyN(ioutil.Discard, bufrw, req.ContentLength); err != nil { + t.Error("Failed to read Body", err) + } + bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n") + bufrw.Flush() + conn.Close() + } + + })) + defer ts.Close() + + tests := []struct { + path string + body []byte + sent int + status int + }{ + {path: "/100", body: []byte("hello"), sent: 5, status: 200}, // Got 100 followed by 200, entire body is sent. + {path: "/200", body: []byte("hello"), sent: 0, status: 200}, // Got 200 without 100. body isn't sent. + {path: "/500", body: []byte("hello"), sent: 0, status: 500}, // Got 500 without 100. body isn't sent. + {path: "/keepalive", body: []byte("hello"), sent: 0, status: 500}, // Althogh without Connection:close, body isn't sent. + {path: "/timeout", body: []byte("hello"), sent: 5, status: 200}, // Timeout exceeded and entire body is sent. + } + + for i, v := range tests { + tr := &Transport{ExpectContinueTimeout: 2 * time.Second} + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + + body := bytes.NewReader(v.body) + req, err := NewRequest("PUT", ts.URL+v.path, body) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Expect", "100-continue") + req.ContentLength = int64(len(v.body)) + + resp, err := c.Do(req) + if err != nil { + t.Fatal(err) + } + resp.Body.Close() + + sent := len(v.body) - body.Len() + if v.status != resp.StatusCode { + t.Errorf("test %d: status code should be %d but got %d. (%s)", i, v.status, resp.StatusCode, v.path) + } + if v.sent != sent { + t.Errorf("test %d: sent body should be %d but sent %d. (%s)", i, v.sent, sent, v.path) + } + } +} + func TestTransportProxy(t *testing.T) { defer afterTest(t) ch := make(chan string, 1) @@ -874,9 +970,7 @@ func TestTransportGzipShort(t *testing.T) { // 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 https://golang.org/issue/7237") - } + setParallel(t) defer afterTest(t) gotReqCh := make(chan bool) unblockCh := make(chan bool) @@ -943,9 +1037,7 @@ 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 https://golang.org/issue/7237") - } + setParallel(t) defer afterTest(t) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { })) @@ -1286,6 +1378,7 @@ func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) { } func TestTransportResponseHeaderTimeout(t *testing.T) { + setParallel(t) defer afterTest(t) if testing.Short() { t.Skip("skipping timeout test in -short mode") @@ -1357,6 +1450,7 @@ func TestTransportResponseHeaderTimeout(t *testing.T) { } func TestTransportCancelRequest(t *testing.T) { + setParallel(t) defer afterTest(t) if testing.Short() { t.Skip("skipping test in -short mode") @@ -1466,6 +1560,7 @@ Get = Get http://something.no-network.tld/: net/http: request canceled while wai } func TestCancelRequestWithChannel(t *testing.T) { + setParallel(t) defer afterTest(t) if testing.Short() { t.Skip("skipping test in -short mode") @@ -1523,6 +1618,7 @@ func TestCancelRequestWithChannel(t *testing.T) { } func TestCancelRequestWithChannelBeforeDo(t *testing.T) { + setParallel(t) defer afterTest(t) unblockc := make(chan bool) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -1554,7 +1650,6 @@ func TestCancelRequestWithChannelBeforeDo(t *testing.T) { // Issue 11020. The returned error message should be errRequestCanceled func TestTransportCancelBeforeResponseHeaders(t *testing.T) { - t.Skip("Skipping flaky test; see Issue 11894") defer afterTest(t) serverConnCh := make(chan net.Conn, 1) @@ -1704,6 +1799,19 @@ func TestTransportNoHost(t *testing.T) { } } +// Issue 13311 +func TestTransportEmptyMethod(t *testing.T) { + req, _ := NewRequest("GET", "http://foo.com/", nil) + req.Method = "" // docs say "For client requests an empty string means GET" + got, err := httputil.DumpRequestOut(req, false) // DumpRequestOut uses Transport + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(got), "GET ") { + t.Fatalf("expected substring 'GET '; got: %s", got) + } +} + func TestTransportSocketLateBinding(t *testing.T) { defer afterTest(t) @@ -2291,15 +2399,103 @@ type errorReader struct { func (e errorReader) Read(p []byte) (int, error) { return 0, e.err } +type plan9SleepReader struct{} + +func (plan9SleepReader) Read(p []byte) (int, error) { + if runtime.GOOS == "plan9" { + // After the fix to unblock TCP Reads in + // https://golang.org/cl/15941, this sleep is required + // on plan9 to make sure TCP Writes before an + // immediate TCP close go out on the wire. On Plan 9, + // it seems that a hangup of a TCP connection with + // queued data doesn't send the queued data first. + // https://golang.org/issue/9554 + time.Sleep(50 * time.Millisecond) + } + return 0, io.EOF +} + type closerFunc func() error func (f closerFunc) Close() error { return f() } +// Issue 4677. If we try to reuse a connection that the server is in the +// process of closing, we may end up successfully writing out our request (or a +// portion of our request) only to find a connection error when we try to read +// from (or finish writing to) the socket. +// +// NOTE: we resend a request only if the request is idempotent, we reused a +// keep-alive connection, and we haven't yet received any header data. This +// automatically prevents an infinite resend loop because we'll run out of the +// cached keep-alive connections eventually. +func TestRetryIdempotentRequestsOnError(t *testing.T) { + defer afterTest(t) + + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + })) + defer ts.Close() + + tr := &Transport{} + c := &Client{Transport: tr} + + const N = 2 + retryc := make(chan struct{}, N) + SetRoundTripRetried(func() { + retryc <- struct{}{} + }) + defer SetRoundTripRetried(nil) + + for n := 0; n < 100; n++ { + // open 2 conns + errc := make(chan error, N) + for i := 0; i < N; i++ { + // start goroutines, send on errc + go func() { + res, err := c.Get(ts.URL) + if err == nil { + res.Body.Close() + } + errc <- err + }() + } + for i := 0; i < N; i++ { + if err := <-errc; err != nil { + t.Fatal(err) + } + } + + ts.CloseClientConnections() + for i := 0; i < N; i++ { + go func() { + res, err := c.Get(ts.URL) + if err == nil { + res.Body.Close() + } + errc <- err + }() + } + + for i := 0; i < N; i++ { + if err := <-errc; err != nil { + t.Fatal(err) + } + } + for i := 0; i < N; i++ { + select { + case <-retryc: + // we triggered a retry, test was successful + t.Logf("finished after %d runs\n", n) + return + default: + } + } + } + t.Fatal("did not trigger any retries") +} + // Issue 6981 func TestTransportClosesBodyOnError(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping test; see https://golang.org/issue/7782") - } + setParallel(t) defer afterTest(t) readBody := make(chan error, 1) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -2313,7 +2509,7 @@ func TestTransportClosesBodyOnError(t *testing.T) { io.Reader io.Closer }{ - io.MultiReader(io.LimitReader(neverEnding('x'), 1<<20), errorReader{fakeErr}), + io.MultiReader(io.LimitReader(neverEnding('x'), 1<<20), plan9SleepReader{}, errorReader{fakeErr}), closerFunc(func() error { select { case didClose <- true: @@ -2474,52 +2670,6 @@ func TestTransportRangeAndGzip(t *testing.T) { res.Body.Close() } -// Previously, we used to handle a logical race within RoundTrip by waiting for 100ms -// in the case of an error. Changing the order of the channel operations got rid of this -// race. -// -// In order to test that the channel op reordering works, we install a hook into the -// roundTrip function which gets called if we saw the connection go away and -// we subsequently received a response. -func TestTransportResponseCloseRace(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - defer afterTest(t) - - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - })) - defer ts.Close() - sawRace := false - SetInstallConnClosedHook(func() { - sawRace = true - }) - defer SetInstallConnClosedHook(nil) - tr := &Transport{ - DisableKeepAlives: true, - } - req, err := NewRequest("GET", ts.URL, nil) - if err != nil { - t.Fatal(err) - } - // selects are not deterministic, so do this a bunch - // and see if we handle the logical race at least once. - for i := 0; i < 10000; i++ { - resp, err := tr.RoundTrip(req) - if err != nil { - t.Fatalf("unexpected error: %s", err) - continue - } - resp.Body.Close() - if sawRace { - break - } - } - if !sawRace { - t.Errorf("didn't see response/connection going away race") - } -} - // Test for issue 10474 func TestTransportResponseCancelRace(t *testing.T) { defer afterTest(t) @@ -2645,7 +2795,7 @@ func TestTransportFlushesBodyChunks(t *testing.T) { req.Header.Set("User-Agent", "x") // known value for test res, err := tr.RoundTrip(req) if err != nil { - t.Error("RoundTrip: %v", err) + t.Errorf("RoundTrip: %v", err) close(resc) return } @@ -2735,6 +2885,153 @@ func TestTransportPrefersResponseOverWriteError(t *testing.T) { } } +func TestTransportAutomaticHTTP2(t *testing.T) { + tr := &Transport{} + _, err := tr.RoundTrip(new(Request)) + if err == nil { + t.Error("expected error from RoundTrip") + } + if tr.TLSNextProto["h2"] == nil { + t.Errorf("HTTP/2 not registered.") + } + + // Now with TLSNextProto set: + tr = &Transport{TLSNextProto: make(map[string]func(string, *tls.Conn) RoundTripper)} + _, err = tr.RoundTrip(new(Request)) + if err == nil { + t.Error("expected error from RoundTrip") + } + if tr.TLSNextProto["h2"] != nil { + t.Errorf("HTTP/2 registered, despite non-nil TLSNextProto field") + } +} + +// Issue 13633: there was a race where we returned bodyless responses +// to callers before recycling the persistent connection, which meant +// a client doing two subsequent requests could end up on different +// connections. It's somewhat harmless but enough tests assume it's +// not true in order to test other things that it's worth fixing. +// Plus it's nice to be consistent and not have timing-dependent +// behavior. +func TestTransportReuseConnEmptyResponseBody(t *testing.T) { + defer afterTest(t) + cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("X-Addr", r.RemoteAddr) + // Empty response body. + })) + defer cst.close() + n := 100 + if testing.Short() { + n = 10 + } + var firstAddr string + for i := 0; i < n; i++ { + res, err := cst.c.Get(cst.ts.URL) + if err != nil { + log.Fatal(err) + } + addr := res.Header.Get("X-Addr") + if i == 0 { + firstAddr = addr + } else if addr != firstAddr { + t.Fatalf("On request %d, addr %q != original addr %q", i+1, addr, firstAddr) + } + res.Body.Close() + } +} + +// Issue 13839 +func TestNoCrashReturningTransportAltConn(t *testing.T) { + cert, err := tls.X509KeyPair(internal.LocalhostCert, internal.LocalhostKey) + if err != nil { + t.Fatal(err) + } + ln := newLocalListener(t) + defer ln.Close() + + handledPendingDial := make(chan bool, 1) + SetPendingDialHooks(nil, func() { handledPendingDial <- true }) + defer SetPendingDialHooks(nil, nil) + + testDone := make(chan struct{}) + defer close(testDone) + go func() { + tln := tls.NewListener(ln, &tls.Config{ + NextProtos: []string{"foo"}, + Certificates: []tls.Certificate{cert}, + }) + sc, err := tln.Accept() + if err != nil { + t.Error(err) + return + } + if err := sc.(*tls.Conn).Handshake(); err != nil { + t.Error(err) + return + } + <-testDone + sc.Close() + }() + + addr := ln.Addr().String() + + req, _ := NewRequest("GET", "https://fake.tld/", nil) + cancel := make(chan struct{}) + req.Cancel = cancel + + doReturned := make(chan bool, 1) + madeRoundTripper := make(chan bool, 1) + + tr := &Transport{ + DisableKeepAlives: true, + TLSNextProto: map[string]func(string, *tls.Conn) RoundTripper{ + "foo": func(authority string, c *tls.Conn) RoundTripper { + madeRoundTripper <- true + return funcRoundTripper(func() { + t.Error("foo RoundTripper should not be called") + }) + }, + }, + Dial: func(_, _ string) (net.Conn, error) { + panic("shouldn't be called") + }, + DialTLS: func(_, _ string) (net.Conn, error) { + tc, err := tls.Dial("tcp", addr, &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"foo"}, + }) + if err != nil { + return nil, err + } + if err := tc.Handshake(); err != nil { + return nil, err + } + close(cancel) + <-doReturned + return tc, nil + }, + } + c := &Client{Transport: tr} + + _, err = c.Do(req) + if ue, ok := err.(*url.Error); !ok || ue.Err != ExportErrRequestCanceledConn { + t.Fatalf("Do error = %v; want url.Error with errRequestCanceledConn", err) + } + + doReturned <- true + <-madeRoundTripper + <-handledPendingDial +} + +var errFakeRoundTrip = errors.New("fake roundtrip") + +type funcRoundTripper func() + +func (fn funcRoundTripper) RoundTrip(*Request) (*Response, error) { + fn() + return nil, errFakeRoundTrip +} + func wantBody(res *Response, err error, want string) error { if err != nil { return err |