summaryrefslogtreecommitdiff
path: root/libgo/go/net/http/transport_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/net/http/transport_test.go')
-rw-r--r--libgo/go/net/http/transport_test.go415
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