/third_party/gofrontend/libgo/go/net/http/transport_test.go
Go | 2810 lines | 2370 code | 258 blank | 182 comment | 710 complexity | aa7b25274556186e13ea52a86c49e028 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
- // Copyright 2011 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.
- // Tests for transport.go
- package http_test
- import (
- "bufio"
- "bytes"
- "compress/gzip"
- "crypto/rand"
- "crypto/tls"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net"
- . "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "reflect"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "testing"
- "time"
- )
- // TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close
- // and then verify that the final 2 responses get errors back.
- // hostPortHandler writes back the client's "host:port".
- var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
- if r.FormValue("close") == "true" {
- w.Header().Set("Connection", "close")
- }
- w.Header().Set("X-Saw-Close", fmt.Sprint(r.Close))
- w.Write([]byte(r.RemoteAddr))
- })
- // testCloseConn is a net.Conn tracked by a testConnSet.
- type testCloseConn struct {
- net.Conn
- set *testConnSet
- }
- func (c *testCloseConn) Close() error {
- c.set.remove(c)
- return c.Conn.Close()
- }
- // testConnSet tracks a set of TCP connections and whether they've
- // 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
- }
- func (tcs *testConnSet) insert(c net.Conn) {
- 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.mu.Lock()
- defer tcs.mu.Unlock()
- tcs.closed[c] = true
- }
- // some tests use this to manage raw tcp connections for later inspection
- func makeTestDial(t *testing.T) (*testConnSet, func(n, addr string) (net.Conn, error)) {
- connSet := &testConnSet{
- t: t,
- closed: make(map[net.Conn]bool),
- }
- dial := func(n, addr string) (net.Conn, error) {
- c, err := net.Dial(n, addr)
- if err != nil {
- return nil, err
- }
- tc := &testCloseConn{c, connSet}
- connSet.insert(tc)
- return tc, nil
- }
- return connSet, dial
- }
- func (tcs *testConnSet) check(t *testing.T) {
- 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))
- }
- }
- }
- // Two subsequent requests and verify their response is the same.
- // The response from the server is our own IP:port
- func TestTransportKeepAlives(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- for _, disableKeepAlive := range []bool{false, true} {
- tr := &Transport{DisableKeepAlives: disableKeepAlive}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- fetch := func(n int) string {
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Fatalf("error in disableKeepAlive=%v, req #%d, GET: %v", disableKeepAlive, n, err)
- }
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("error in disableKeepAlive=%v, req #%d, ReadAll: %v", disableKeepAlive, n, err)
- }
- return string(body)
- }
- body1 := fetch(1)
- body2 := fetch(2)
- bodiesDiffer := body1 != body2
- if bodiesDiffer != disableKeepAlive {
- t.Errorf("error in disableKeepAlive=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
- disableKeepAlive, bodiesDiffer, body1, body2)
- }
- }
- }
- func TestTransportConnectionCloseOnResponse(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- connSet, testDial := makeTestDial(t)
- for _, connectionClose := range []bool{false, true} {
- tr := &Transport{
- Dial: testDial,
- }
- c := &Client{Transport: tr}
- fetch := func(n int) string {
- req := new(Request)
- var err error
- req.URL, err = url.Parse(ts.URL + fmt.Sprintf("/?close=%v", connectionClose))
- if err != nil {
- t.Fatalf("URL parse error: %v", err)
- }
- req.Method = "GET"
- req.Proto = "HTTP/1.1"
- req.ProtoMajor = 1
- req.ProtoMinor = 1
- res, err := c.Do(req)
- if err != nil {
- t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err)
- }
- defer res.Body.Close()
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err)
- }
- return string(body)
- }
- body1 := fetch(1)
- body2 := fetch(2)
- bodiesDiffer := body1 != body2
- if bodiesDiffer != connectionClose {
- t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
- connectionClose, bodiesDiffer, body1, body2)
- }
- tr.CloseIdleConnections()
- }
- connSet.check(t)
- }
- func TestTransportConnectionCloseOnRequest(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- connSet, testDial := makeTestDial(t)
- for _, connectionClose := range []bool{false, true} {
- tr := &Transport{
- Dial: testDial,
- }
- c := &Client{Transport: tr}
- fetch := func(n int) string {
- req := new(Request)
- var err error
- req.URL, err = url.Parse(ts.URL)
- if err != nil {
- t.Fatalf("URL parse error: %v", err)
- }
- req.Method = "GET"
- req.Proto = "HTTP/1.1"
- req.ProtoMajor = 1
- req.ProtoMinor = 1
- req.Close = connectionClose
- res, err := c.Do(req)
- if err != nil {
- t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err)
- }
- if got, want := res.Header.Get("X-Saw-Close"), fmt.Sprint(connectionClose); got != want {
- t.Errorf("For connectionClose = %v; handler's X-Saw-Close was %v; want %v",
- connectionClose, got, !connectionClose)
- }
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err)
- }
- return string(body)
- }
- body1 := fetch(1)
- body2 := fetch(2)
- bodiesDiffer := body1 != body2
- if bodiesDiffer != connectionClose {
- t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
- connectionClose, bodiesDiffer, body1, body2)
- }
- tr.CloseIdleConnections()
- }
- connSet.check(t)
- }
- // if the Transport's DisableKeepAlives is set, all requests should
- // send Connection: close.
- func TestTransportConnectionCloseOnRequestDisableKeepAlive(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- tr := &Transport{
- DisableKeepAlives: true,
- }
- c := &Client{Transport: tr}
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- if res.Header.Get("X-Saw-Close") != "true" {
- t.Errorf("handler didn't see Connection: close ")
- }
- }
- func TestTransportIdleCacheKeys(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- tr := &Transport{DisableKeepAlives: false}
- c := &Client{Transport: tr}
- if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
- t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g)
- }
- resp, err := c.Get(ts.URL)
- if err != nil {
- t.Error(err)
- }
- ioutil.ReadAll(resp.Body)
- keys := tr.IdleConnKeysForTesting()
- if e, g := 1, len(keys); e != g {
- t.Fatalf("After Get expected %d idle conn cache keys; got %d", e, g)
- }
- if e := "|http|" + ts.Listener.Addr().String(); keys[0] != e {
- t.Errorf("Expected idle cache key %q; got %q", e, keys[0])
- }
- tr.CloseIdleConnections()
- if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
- t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g)
- }
- }
- // 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.(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 := 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)
- gotReq := make(chan bool)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- gotReq <- true
- msg := <-resch
- _, err := w.Write([]byte(msg))
- if err != nil {
- t.Fatalf("Write: %v", err)
- }
- }))
- defer ts.Close()
- maxIdleConns := 2
- tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns}
- c := &Client{Transport: tr}
- // Start 3 outstanding requests and wait for the server to get them.
- // Their responses will hang until we write to resch, though.
- donech := make(chan bool)
- doReq := func() {
- resp, err := c.Get(ts.URL)
- if err != nil {
- t.Error(err)
- return
- }
- if _, err := ioutil.ReadAll(resp.Body); err != nil {
- t.Errorf("ReadAll: %v", err)
- return
- }
- donech <- true
- }
- go doReq()
- <-gotReq
- go doReq()
- <-gotReq
- go doReq()
- <-gotReq
- if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
- t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g)
- }
- resch <- "res1"
- <-donech
- keys := tr.IdleConnKeysForTesting()
- if e, g := 1, len(keys); e != g {
- t.Fatalf("after first response, expected %d idle conn cache keys; got %d", e, g)
- }
- cacheKey := "|http|" + ts.Listener.Addr().String()
- if keys[0] != cacheKey {
- t.Fatalf("Expected idle cache key %q; got %q", cacheKey, keys[0])
- }
- if e, g := 1, tr.IdleConnCountForTesting(cacheKey); e != g {
- t.Errorf("after first response, expected %d idle conns; got %d", e, g)
- }
- resch <- "res2"
- <-donech
- if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g {
- t.Errorf("after second response, expected %d idle conns; got %d", e, g)
- }
- resch <- "res3"
- <-donech
- if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g {
- t.Errorf("after third response, still expected %d idle conns; got %d", e, g)
- }
- }
- func TestTransportServerClosingUnexpectedly(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(hostPortHandler)
- defer ts.Close()
- tr := &Transport{}
- c := &Client{Transport: tr}
- fetch := func(n, retries int) string {
- condFatalf := func(format string, arg ...interface{}) {
- if retries <= 0 {
- t.Fatalf(format, arg...)
- }
- t.Logf("retrying shortly after expected error: "+format, arg...)
- time.Sleep(time.Second / time.Duration(retries))
- }
- for retries >= 0 {
- retries--
- res, err := c.Get(ts.URL)
- if err != nil {
- condFatalf("error in req #%d, GET: %v", n, err)
- continue
- }
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- condFatalf("error in req #%d, ReadAll: %v", n, err)
- continue
- }
- res.Body.Close()
- return string(body)
- }
- panic("unreachable")
- }
- body1 := fetch(1, 0)
- body2 := fetch(2, 0)
- ts.CloseClientConnections() // surprise!
- // This test has an expected race. Sleeping for 25 ms prevents
- // it on most fast machines, causing the next fetch() call to
- // succeed quickly. But if we do get errors, fetch() will retry 5
- // times with some delays between.
- time.Sleep(25 * time.Millisecond)
- body3 := fetch(3, 5)
- if body1 != body2 {
- t.Errorf("expected body1 and body2 to be equal")
- }
- if body2 == body3 {
- t.Errorf("expected body2 and body3 to be different")
- }
- }
- // Test for https://golang.org/issue/2616 (appropriate issue number)
- // This fails pretty reliably with GOMAXPROCS=100 or something high.
- func TestStressSurpriseServerCloses(t *testing.T) {
- defer afterTest(t)
- if testing.Short() {
- t.Skip("skipping test in short mode")
- }
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- w.Header().Set("Content-Length", "5")
- w.Header().Set("Content-Type", "text/plain")
- w.Write([]byte("Hello"))
- w.(Flusher).Flush()
- conn, buf, _ := w.(Hijacker).Hijack()
- buf.Flush()
- conn.Close()
- }))
- defer ts.Close()
- tr := &Transport{DisableKeepAlives: false}
- c := &Client{Transport: tr}
- defer tr.CloseIdleConnections()
- // Do a bunch of traffic from different goroutines. Send to activityc
- // after each request completes, regardless of whether it failed.
- // If these are too high, OS X exhausts its ephemeral ports
- // and hangs waiting for them to transition TCP states. That's
- // not what we want to test. TODO(bradfitz): use an io.Pipe
- // dialer for this test instead?
- const (
- numClients = 20
- reqsPerClient = 25
- )
- activityc := make(chan bool)
- for i := 0; i < numClients; i++ {
- go func() {
- for i := 0; i < reqsPerClient; i++ {
- res, err := c.Get(ts.URL)
- if err == nil {
- // We expect errors since the server is
- // hanging up on us after telling us to
- // send more requests, so we don't
- // actually care what the error is.
- // But we want to close the body in cases
- // where we won the race.
- res.Body.Close()
- }
- activityc <- true
- }
- }()
- }
- // Make sure all the request come back, one way or another.
- for i := 0; i < numClients*reqsPerClient; i++ {
- select {
- case <-activityc:
- case <-time.After(5 * time.Second):
- t.Fatalf("presumed deadlock; no HTTP client activity seen in awhile")
- }
- }
- }
- // TestTransportHeadResponses verifies that we deal with Content-Lengths
- // with no bodies properly
- func TestTransportHeadResponses(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- if r.Method != "HEAD" {
- panic("expected HEAD; got " + r.Method)
- }
- w.Header().Set("Content-Length", "123")
- w.WriteHeader(200)
- }))
- defer ts.Close()
- tr := &Transport{DisableKeepAlives: false}
- c := &Client{Transport: tr}
- for i := 0; i < 2; i++ {
- res, err := c.Head(ts.URL)
- if err != nil {
- t.Errorf("error on loop %d: %v", i, err)
- continue
- }
- if e, g := "123", res.Header.Get("Content-Length"); e != g {
- t.Errorf("loop %d: expected Content-Length header of %q, got %q", i, e, g)
- }
- if e, g := int64(123), res.ContentLength; e != g {
- t.Errorf("loop %d: expected res.ContentLength of %v, got %v", i, e, g)
- }
- if all, err := ioutil.ReadAll(res.Body); err != nil {
- t.Errorf("loop %d: Body ReadAll: %v", i, err)
- } else if len(all) != 0 {
- t.Errorf("Bogus body %q", all)
- }
- }
- }
- // TestTransportHeadChunkedResponse verifies that we ignore chunked transfer-encoding
- // on responses to HEAD requests.
- func TestTransportHeadChunkedResponse(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- if r.Method != "HEAD" {
- panic("expected HEAD; got " + r.Method)
- }
- w.Header().Set("Transfer-Encoding", "chunked") // client should ignore
- w.Header().Set("x-client-ipport", r.RemoteAddr)
- w.WriteHeader(200)
- }))
- defer ts.Close()
- tr := &Transport{DisableKeepAlives: false}
- c := &Client{Transport: tr}
- // Ensure that we wait for the readLoop to complete before
- // calling Head again
- didRead := make(chan bool)
- SetReadLoopBeforeNextReadHook(func() { didRead <- true })
- defer SetReadLoopBeforeNextReadHook(nil)
- res1, err := c.Head(ts.URL)
- <-didRead
- if err != nil {
- t.Fatalf("request 1 error: %v", err)
- }
- res2, err := c.Head(ts.URL)
- <-didRead
- if err != nil {
- t.Fatalf("request 2 error: %v", err)
- }
- if v1, v2 := res1.Header.Get("x-client-ipport"), res2.Header.Get("x-client-ipport"); v1 != v2 {
- t.Errorf("ip/ports differed between head requests: %q vs %q", v1, v2)
- }
- }
- var roundTripTests = []struct {
- accept string
- expectAccept string
- compressed bool
- }{
- // Requests with no accept-encoding header use transparent compression
- {"", "gzip", false},
- // Requests with other accept-encoding should pass through unmodified
- {"foo", "foo", false},
- // Requests with accept-encoding == gzip should be passed through
- {"gzip", "gzip", true},
- }
- // Test that the modification made to the Request by the RoundTripper is cleaned up
- func TestRoundTripGzip(t *testing.T) {
- defer afterTest(t)
- const responseBody = "test response body"
- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
- accept := req.Header.Get("Accept-Encoding")
- if expect := req.FormValue("expect_accept"); accept != expect {
- t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",
- req.FormValue("testnum"), accept, expect)
- }
- if accept == "gzip" {
- rw.Header().Set("Content-Encoding", "gzip")
- gz := gzip.NewWriter(rw)
- gz.Write([]byte(responseBody))
- gz.Close()
- } else {
- rw.Header().Set("Content-Encoding", accept)
- rw.Write([]byte(responseBody))
- }
- }))
- defer ts.Close()
- for i, test := range roundTripTests {
- // Test basic request (no accept-encoding)
- req, _ := NewRequest("GET", fmt.Sprintf("%s/?testnum=%d&expect_accept=%s", ts.URL, i, test.expectAccept), nil)
- if test.accept != "" {
- req.Header.Set("Accept-Encoding", test.accept)
- }
- res, err := DefaultTransport.RoundTrip(req)
- var body []byte
- if test.compressed {
- var r *gzip.Reader
- r, err = gzip.NewReader(res.Body)
- if err != nil {
- t.Errorf("%d. gzip NewReader: %v", i, err)
- continue
- }
- body, err = ioutil.ReadAll(r)
- res.Body.Close()
- } else {
- body, err = ioutil.ReadAll(res.Body)
- }
- if err != nil {
- t.Errorf("%d. Error: %q", i, err)
- continue
- }
- if g, e := string(body), responseBody; g != e {
- t.Errorf("%d. body = %q; want %q", i, g, e)
- }
- if g, e := req.Header.Get("Accept-Encoding"), test.accept; g != e {
- t.Errorf("%d. Accept-Encoding = %q; want %q (it was mutated, in violation of RoundTrip contract)", i, g, e)
- }
- if g, e := res.Header.Get("Content-Encoding"), test.accept; g != e {
- t.Errorf("%d. Content-Encoding = %q; want %q", i, g, e)
- }
- }
- }
- func TestTransportGzip(t *testing.T) {
- defer afterTest(t)
- const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- const nRandBytes = 1024 * 1024
- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
- if req.Method == "HEAD" {
- if g := req.Header.Get("Accept-Encoding"); g != "" {
- t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)
- }
- return
- }
- if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e {
- t.Errorf("Accept-Encoding = %q, want %q", g, e)
- }
- rw.Header().Set("Content-Encoding", "gzip")
- var w io.Writer = rw
- var buf bytes.Buffer
- if req.FormValue("chunked") == "0" {
- w = &buf
- defer io.Copy(rw, &buf)
- defer func() {
- rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
- }()
- }
- gz := gzip.NewWriter(w)
- gz.Write([]byte(testString))
- if req.FormValue("body") == "large" {
- io.CopyN(gz, rand.Reader, nRandBytes)
- }
- gz.Close()
- }))
- defer ts.Close()
- for _, chunked := range []string{"1", "0"} {
- c := &Client{Transport: &Transport{}}
- // First fetch something large, but only read some of it.
- res, err := c.Get(ts.URL + "/?body=large&chunked=" + chunked)
- if err != nil {
- t.Fatalf("large get: %v", err)
- }
- buf := make([]byte, len(testString))
- n, err := io.ReadFull(res.Body, buf)
- if err != nil {
- t.Fatalf("partial read of large response: size=%d, %v", n, err)
- }
- if e, g := testString, string(buf); e != g {
- t.Errorf("partial read got %q, expected %q", g, e)
- }
- res.Body.Close()
- // Read on the body, even though it's closed
- n, err = res.Body.Read(buf)
- if n != 0 || err == nil {
- t.Errorf("expected error post-closed large Read; got = %d, %v", n, err)
- }
- // Then something small.
- res, err = c.Get(ts.URL + "/?chunked=" + chunked)
- if err != nil {
- t.Fatal(err)
- }
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(err)
- }
- if g, e := string(body), testString; g != e {
- t.Fatalf("body = %q; want %q", g, e)
- }
- if g, e := res.Header.Get("Content-Encoding"), ""; g != e {
- t.Fatalf("Content-Encoding = %q; want %q", g, e)
- }
- // Read on the body after it's been fully read:
- n, err = res.Body.Read(buf)
- if n != 0 || err == nil {
- t.Errorf("expected Read error after exhausted reads; got %d, %v", n, err)
- }
- res.Body.Close()
- n, err = res.Body.Read(buf)
- if n != 0 || err == nil {
- t.Errorf("expected Read error after Close; got %d, %v", n, err)
- }
- }
- // And a HEAD request too, because they're always weird.
- c := &Client{Transport: &Transport{}}
- res, err := c.Head(ts.URL)
- if err != nil {
- t.Fatalf("Head: %v", err)
- }
- if res.StatusCode != 200 {
- t.Errorf("Head status=%d; want=200", res.StatusCode)
- }
- }
- func TestTransportProxy(t *testing.T) {
- defer afterTest(t)
- ch := make(chan string, 1)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- ch <- "real server"
- }))
- defer ts.Close()
- proxy := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- ch <- "proxy for " + r.URL.String()
- }))
- defer proxy.Close()
- pu, err := url.Parse(proxy.URL)
- if err != nil {
- t.Fatal(err)
- }
- c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
- c.Head(ts.URL)
- got := <-ch
- want := "proxy for " + ts.URL + "/"
- if got != want {
- t.Errorf("want %q, got %q", want, got)
- }
- }
- // TestTransportGzipRecursive sends a gzip quine and checks that the
- // client gets the same value back. This is more cute than anything,
- // but checks that we don't recurse forever, and checks that
- // Content-Encoding is removed.
- func TestTransportGzipRecursive(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- w.Header().Set("Content-Encoding", "gzip")
- w.Write(rgz)
- }))
- defer ts.Close()
- c := &Client{Transport: &Transport{}}
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Fatal(err)
- }
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(body, rgz) {
- t.Fatalf("Incorrect result from recursive gz:\nhave=%x\nwant=%x",
- body, rgz)
- }
- if g, e := res.Header.Get("Content-Encoding"), ""; g != e {
- t.Fatalf("Content-Encoding = %q; want %q", g, e)
- }
- }
- // 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 https://golang.org/issue/7237")
- }
- defer afterTest(t)
- gotReqCh := make(chan bool)
- unblockCh := make(chan bool)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- gotReqCh <- true
- <-unblockCh
- w.Header().Set("Content-Length", "0")
- w.WriteHeader(204)
- }))
- defer ts.Close()
- tr := &Transport{}
- c := &Client{Transport: tr}
- n0 := runtime.NumGoroutine()
- const numReq = 25
- didReqCh := make(chan bool)
- for i := 0; i < numReq; i++ {
- go func() {
- res, err := c.Get(ts.URL)
- didReqCh <- true
- if err != nil {
- t.Errorf("client fetch error: %v", err)
- return
- }
- res.Body.Close()
- }()
- }
- // Wait for all goroutines to be stuck in the Handler.
- for i := 0; i < numReq; i++ {
- <-gotReqCh
- }
- nhigh := runtime.NumGoroutine()
- // Tell all handlers to unblock and reply.
- for i := 0; i < numReq; i++ {
- unblockCh <- true
- }
- // Wait for all HTTP clients to be done.
- for i := 0; i < numReq; i++ {
- <-didReqCh
- }
- tr.CloseIdleConnections()
- time.Sleep(100 * time.Millisecond)
- runtime.GC()
- runtime.GC() // even more.
- nfinal := runtime.NumGoroutine()
- growth := nfinal - n0
- // We expect 0 or 1 extra goroutine, empirically. Allow up to 5.
- // Previously we were leaking one per numReq.
- if int(growth) > 5 {
- t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
- t.Error("too many new goroutines")
- }
- }
- // 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")
- }
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- }))
- defer ts.Close()
- tr := &Transport{}
- c := &Client{Transport: tr}
- n0 := runtime.NumGoroutine()
- body := []byte("Hello")
- for i := 0; i < 20; i++ {
- req, err := NewRequest("POST", ts.URL, bytes.NewReader(body))
- if err != nil {
- t.Fatal(err)
- }
- req.ContentLength = int64(len(body) - 2) // explicitly short
- _, err = c.Do(req)
- if err == nil {
- t.Fatal("Expect an error from writing too long of a body.")
- }
- }
- nhigh := runtime.NumGoroutine()
- tr.CloseIdleConnections()
- time.Sleep(400 * time.Millisecond)
- runtime.GC()
- nfinal := runtime.NumGoroutine()
- growth := nfinal - n0
- // 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.Error("too many new goroutines")
- }
- }
- // This used to crash; https://golang.org/issue/3266
- func TestTransportIdleConnCrash(t *testing.T) {
- defer afterTest(t)
- tr := &Transport{}
- c := &Client{Transport: tr}
- unblockCh := make(chan bool, 1)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- <-unblockCh
- tr.CloseIdleConnections()
- }))
- defer ts.Close()
- didreq := make(chan bool)
- go func() {
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Error(err)
- } else {
- res.Body.Close() // returns idle conn
- }
- didreq <- true
- }()
- unblockCh <- true
- <-didreq
- }
- // Test that the transport doesn't close the TCP connection early,
- // before the response body has been read. This was a regression
- // which sadly lacked a triggering test. The large response body made
- // the old race easier to trigger.
- func TestIssue3644(t *testing.T) {
- defer afterTest(t)
- const numFoos = 5000
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- w.Header().Set("Connection", "close")
- for i := 0; i < numFoos; i++ {
- w.Write([]byte("foo "))
- }
- }))
- defer ts.Close()
- tr := &Transport{}
- c := &Client{Transport: tr}
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Fatal(err)
- }
- defer res.Body.Close()
- bs, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(err)
- }
- if len(bs) != numFoos*len("foo ") {
- t.Errorf("unexpected response length")
- }
- }
- // Test that a client receives a server's reply, even if the server doesn't read
- // the entire request body.
- func TestIssue3595(t *testing.T) {
- defer afterTest(t)
- const deniedMsg = "sorry, denied."
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- Error(w, deniedMsg, StatusUnauthorized)
- }))
- defer ts.Close()
- tr := &Transport{}
- c := &Client{Transport: tr}
- res, err := c.Post(ts.URL, "application/octet-stream", neverEnding('a'))
- if err != nil {
- t.Errorf("Post: %v", err)
- return
- }
- got, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("Body ReadAll: %v", err)
- }
- if !strings.Contains(string(got), deniedMsg) {
- t.Errorf("Known bug: response %q does not contain %q", got, deniedMsg)
- }
- }
- // From https://golang.org/issue/4454 ,
- // "client fails to handle requests with no body and chunked encoding"
- func TestChunkedNoContent(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- w.WriteHeader(StatusNoContent)
- }))
- defer ts.Close()
- for _, closeBody := range []bool{true, false} {
- c := &Client{Transport: &Transport{}}
- const n = 4
- for i := 1; i <= n; i++ {
- res, err := c.Get(ts.URL)
- if err != nil {
- t.Errorf("closingBody=%v, req %d/%d: %v", closeBody, i, n, err)
- } else {
- if closeBody {
- res.Body.Close()
- }
- }
- }
- }
- }
- func TestTransportConcurrency(t *testing.T) {
- defer afterTest(t)
- maxProcs, numReqs := 16, 500
- if testing.Short() {
- maxProcs, numReqs = 4, 50
- }
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- fmt.Fprintf(w, "%v", r.FormValue("echo"))
- }))
- defer ts.Close()
- var wg sync.WaitGroup
- wg.Add(numReqs)
- // Due to the Transport's "socket late binding" (see
- // idleConnCh in transport.go), the numReqs HTTP requests
- // below can finish with a dial still outstanding. To keep
- // the leak checker happy, keep track of pending dials and
- // wait for them to finish (and be closed or returned to the
- // idle pool) before we close idle connections.
- SetPendingDialHooks(func() { wg.Add(1) }, wg.Done)
- defer SetPendingDialHooks(nil, nil)
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- reqs := make(chan string)
- defer close(reqs)
- for i := 0; i < maxProcs*2; i++ {
- go func() {
- for req := range reqs {
- res, err := c.Get(ts.URL + "/?echo=" + req)
- if err != nil {
- t.Errorf("error on req %s: %v", req, err)
- wg.Done()
- continue
- }
- all, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Errorf("read error on req %s: %v", req, err)
- wg.Done()
- continue
- }
- if string(all) != req {
- t.Errorf("body of req %s = %q; want %q", req, all, req)
- }
- res.Body.Close()
- wg.Done()
- }
- }()
- }
- for i := 0; i < numReqs; i++ {
- reqs <- fmt.Sprintf("request-%d", i)
- }
- wg.Wait()
- }
- func TestIssue4191_InfiniteGetTimeout(t *testing.T) {
- if runtime.GOOS == "plan9" {
- t.Skip("skipping test; see https://golang.org/issue/7237")
- }
- defer afterTest(t)
- const debug = false
- mux := NewServeMux()
- mux.HandleFunc("/get", func(w ResponseWriter, r *Request) {
- io.Copy(w, neverEnding('a'))
- })
- ts := httptest.NewServer(mux)
- timeout := 100 * time.Millisecond
- client := &Client{
- Transport: &Transport{
- Dial: func(n, addr string) (net.Conn, error) {
- conn, err := net.Dial(n, addr)
- if err != nil {
- return nil, err
- }
- conn.SetDeadline(time.Now().Add(timeout))
- if debug {
- conn = NewLoggingConn("client", conn)
- }
- return conn, nil
- },
- DisableKeepAlives: true,
- },
- }
- getFailed := false
- nRuns := 5
- if testing.Short() {
- nRuns = 1
- }
- for i := 0; i < nRuns; i++ {
- if debug {
- println("run", i+1, "of", nRuns)
- }
- sres, err := client.Get(ts.URL + "/get")
- if err != nil {
- if !getFailed {
- // Make the timeout longer, once.
- getFailed = true
- t.Logf("increasing timeout")
- i--
- timeout *= 10
- continue
- }
- t.Errorf("Error issuing GET: %v", err)
- break
- }
- _, err = io.Copy(ioutil.Discard, sres.Body)
- if err == nil {
- t.Errorf("Unexpected successful copy")
- break
- }
- }
- if debug {
- println("tests complete; waiting for handlers to finish")
- }
- ts.Close()
- }
- func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) {
- if runtime.GOOS == "plan9" {
- t.Skip("skipping test; see https://golang.org/issue/7237")
- }
- defer afterTest(t)
- const debug = false
- mux := NewServeMux()
- mux.HandleFunc("/get", func(w ResponseWriter, r *Request) {
- io.Copy(w, neverEnding('a'))
- })
- mux.HandleFunc("/put", func(w ResponseWriter, r *Request) {
- defer r.Body.Close()
- io.Copy(ioutil.Discard, r.Body)
- })
- ts := httptest.NewServer(mux)
- timeout := 100 * time.Millisecond
- client := &Client{
- Transport: &Transport{
- Dial: func(n, addr string) (net.Conn, error) {
- conn, err := net.Dial(n, addr)
- if err != nil {
- return nil, err
- }
- conn.SetDeadline(time.Now().Add(timeout))
- if debug {
- conn = NewLoggingConn("client", conn)
- }
- return conn, nil
- },
- DisableKeepAlives: true,
- },
- }
- getFailed := false
- nRuns := 5
- if testing.Short() {
- nRuns = 1
- }
- for i := 0; i < nRuns; i++ {
- if debug {
- println("run", i+1, "of", nRuns)
- }
- sres, err := client.Get(ts.URL + "/get")
- if err != nil {
- if !getFailed {
- // Make the timeout longer, once.
- getFailed = true
- t.Logf("increasing timeout")
- i--
- timeout *= 10
- continue
- }
- t.Errorf("Error issuing GET: %v", err)
- break
- }
- req, _ := NewRequest("PUT", ts.URL+"/put", sres.Body)
- _, err = client.Do(req)
- if err == nil {
- sres.Body.Close()
- t.Errorf("Unexpected successful PUT")
- break
- }
- sres.Body.Close()
- }
- if debug {
- println("tests complete; waiting for handlers to finish")
- }
- ts.Close()
- }
- func TestTransportResponseHeaderTimeout(t *testing.T) {
- defer afterTest(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) {
- inHandler <- true
- })
- mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {
- inHandler <- true
- time.Sleep(2 * time.Second)
- })
- ts := httptest.NewServer(mux)
- defer ts.Close()
- tr := &Transport{
- ResponseHeaderTimeout: 500 * time.Millisecond,
- }
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- tests := []struct {
- path string
- want int
- wantErr string
- }{
- {path: "/fast", want: 200},
- {path: "/slow", wantErr: "timeout awaiting response headers"},
- {path: "/fast", want: 200},
- }
- 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
- }
- t.Errorf("%d. unexpected error: %v", i, err)
- continue
- }
- if tt.wantErr != "" {
- t.Errorf("%d. no error. expected error: %v", i, tt.wantErr)
- continue
- }
- if res.StatusCode != tt.want {
- t.Errorf("%d for path %q status = %d; want %d", i, tt.path, res.StatusCode, tt.want)
- }
- }
- }
- func TestTransportCancelRequest(t *testing.T) {
- defer afterTest(t)
- if testing.Short() {
- t.Skip("skipping test in -short mode")
- }
- unblockc := make(chan bool)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- fmt.Fprintf(w, "Hello")
- w.(Flusher).Flush() // send headers and some body
- <-unblockc
- }))
- defer ts.Close()
- defer close(unblockc)
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- req, _ := NewRequest("GET", ts.URL, nil)
- res, err := c.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- go func() {
- time.Sleep(1 * time.Second)
- tr.CancelRequest(req)
- }()
- t0 := time.Now()
- body, err := ioutil.ReadAll(res.Body)
- d := time.Since(t0)
- if err != ExportErrRequestCanceled {
- t.Errorf("Body.Read error = %v; want errRequestCanceled", err)
- }
- if string(body) != "Hello" {
- t.Errorf("Body = %q; want Hello", body)
- }
- if d < 500*time.Millisecond {
- t.Errorf("expected ~1 second delay; got %v", d)
- }
- // Verify no outstanding requests after readLoop/writeLoop
- // goroutines shut down.
- for tries := 5; tries > 0; tries-- {
- n := tr.NumPendingRequestsForTesting()
- if n == 0 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- if tries == 1 {
- t.Errorf("pending requests = %d; want 0", n)
- }
- }
- }
- 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)
- tr.CancelRequest(req) // used to panic on second call
- 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)
- }
- }
- func TestCancelRequestWithChannel(t *testing.T) {
- defer afterTest(t)
- if testing.Short() {
- t.Skip("skipping test in -short mode")
- }
- unblockc := make(chan bool)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- fmt.Fprintf(w, "Hello")
- w.(Flusher).Flush() // send headers and some body
- <-unblockc
- }))
- defer ts.Close()
- defer close(unblockc)
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- req, _ := NewRequest("GET", ts.URL, nil)
- ch := make(chan struct{})
- req.Cancel = ch
- res, err := c.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- go func() {
- time.Sleep(1 * time.Second)
- close(ch)
- }()
- t0 := time.Now()
- body, err := ioutil.ReadAll(res.Body)
- d := time.Since(t0)
- if err != ExportErrRequestCanceled {
- t.Errorf("Body.Read error = %v; want errRequestCanceled", err)
- }
- if string(body) != "Hello" {
- t.Errorf("Body = %q; want Hello", body)
- }
- if d < 500*time.Millisecond {
- t.Errorf("expected ~1 second delay; got %v", d)
- }
- // Verify no outstanding requests after readLoop/writeLoop
- // goroutines shut down.
- for tries := 5; tries > 0; tries-- {
- n := tr.NumPendingRequestsForTesting()
- if n == 0 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- if tries == 1 {
- t.Errorf("pending requests = %d; want 0", n)
- }
- }
- }
- func TestCancelRequestWithChannelBeforeDo(t *testing.T) {
- defer afterTest(t)
- unblockc := make(chan bool)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- <-unblockc
- }))
- defer ts.Close()
- defer close(unblockc)
- // Don't interfere with the next test on plan9.
- // Cf. https://golang.org/issues/11476
- if runtime.GOOS == "plan9" {
- defer time.Sleep(500 * time.Millisecond)
- }
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- req, _ := NewRequest("GET", ts.URL, nil)
- ch := make(chan struct{})
- req.Cancel = ch
- close(ch)
- _, err := c.Do(req)
- if err == nil || !strings.Contains(err.Error(), "canceled") {
- t.Errorf("Do error = %v; want cancelation", err)
- }
- }
- // 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)
- tr := &Transport{
- Dial: func(network, addr string) (net.Conn, error) {
- cc, sc := net.Pipe()
- serverConnCh <- sc
- return cc, nil
- },
- }
- defer tr.CloseIdleConnections()
- errc := make(chan error, 1)
- req, _ := NewRequest("GET", "http://example.com/", nil)
- go func() {
- _, err := tr.RoundTrip(req)
- errc <- err
- }()
- sc := <-serverConnCh
- verb := make([]byte, 3)
- if _, err := io.ReadFull(sc, verb); err != nil {
- t.Errorf("Error reading HTTP verb from server: %v", err)
- }
- if string(verb) != "GET" {
- t.Errorf("server received %q; want GET", verb)
- }
- defer sc.Close()
- tr.CancelRequest(req)
- err := <-errc
- if err == nil {
- t.Fatalf("unexpected success from RoundTrip")
- }
- if err != ExportErrRequestCanceled {
- t.Errorf("RoundTrip error = %v; want ExportErrRequestCanceled", err)
- }
- }
- // 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.
- func TestTransportCloseResponseBody(t *testing.T) {
- defer afterTest(t)
- writeErr := make(chan error, 1)
- msg := []byte("young\n")
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- for {
- _, err := w.Write(msg)
- if err != nil {
- writeErr <- err
- return
- }
- w.(Flusher).Flush()
- }
- }))
- defer ts.Close()
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- req, _ := NewRequest("GET", ts.URL, nil)
- defer tr.CancelRequest(req)
- res, err := c.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- const repeats = 3
- buf := make([]byte, len(msg)*repeats)
- want := bytes.Repeat(msg, repeats)
- _, err = io.ReadFull(res.Body, buf)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(buf, want) {
- t.Fatalf("read %q; want %q", buf, want)
- }
- didClose := make(chan error, 1)
- go func() {
- didClose <- res.Body.Close()
- }()
- select {
- case err := <-didClose:
- if err != nil {
- t.Errorf("Close = %v", err)
- }
- case <-time.After(10 * time.Second):
- t.Fatal("too long waiting for close")
- }
- select {
- case err := <-writeErr:
- if err == nil {
- t.Errorf("expected non-nil write error")
- }
- case <-time.After(10 * time.Second):
- t.Fatal("too long waiting for write error")
- }
- }
- type fooProto struct{}
- func (fooProto) RoundTrip(req *Request) (*Response, error) {
- res := &Response{
- Status: "200 OK",
- StatusCode: 200,
- Header: make(Header),
- Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())),
- }
- return res, nil
- }
- func TestTransportAltProto(t *testing.T) {
- defer afterTest(t)
- tr := &Transport{}
- c := &Client{Transport: tr}
- tr.RegisterProtocol("foo", fooProto{})
- res, err := c.Get("foo://bar.com/path")
- if err != nil {
- t.Fatal(err)
- }
- bodyb, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(err)
- }
- body := string(bodyb)
- if e := "You wanted foo://bar.com/path"; body != e {
- t.Errorf("got response %q, want %q", body, e)
- }
- }
- func TestTransportNoHost(t *testing.T) {
- defer afterTest(t)
- tr := &Transport{}
- _, err := tr.RoundTrip(&Request{
- Header: make(Header),
- URL: &url.URL{
- Scheme: "http",
- },
- })
- want := "http: no Host in request URL"
- if got := fmt.Sprint(err); got != want {
- t.Errorf("error = %v; want %q", err, want)
- }
- }
- func TestTransportSocketLateBinding(t *testing.T) {
- defer afterTest(t)
- mux := NewServeMux()
- fooGate := make(chan bool, 1)
- mux.HandleFunc("/foo", func(w ResponseWriter, r *Request) {
- w.Header().Set("foo-ipport", r.RemoteAddr)
- w.(Flusher).Flush()
- <-fooGate
- })
- mux.HandleFunc("/bar", func(w ResponseWriter, r *Request) {
- w.Header().Set("bar-ipport", r.RemoteAddr)
- })
- ts := httptest.NewServer(mux)
- defer ts.Close()
- dialGate := make(chan bool, 1)
- tr := &Transport{
- Dial: func(n, addr string) (net.Conn, error) {
- if <-dialGate {
- return net.Dial(n, addr)
- }
- return nil, errors.New("manually closed")
- },
- DisableKeepAlives: false,
- }
- defer tr.CloseIdleConnections()
- c := &Client{
- Transport: tr,
- }
- dialGate <- true // only allow one dial
- fooRes, err := c.Get(ts.URL + "/foo")
- if err != nil {
- t.Fatal(err)
- }
- fooAddr := fooRes.Header.Get("foo-ipport")
- if fooAddr == "" {
- t.Fatal("No addr on /foo request")
- }
- time.AfterFunc(200*time.Millisecond, func() {
- // let the foo response finish so we can use its
- // connection for /bar
- fooGate <- true
- io.Copy(ioutil.Discard, fooRes.Body)
- fooRes.Body.Close()
- })
- barRes, err := c.Get(ts.URL + "/bar")
- if err != nil {
- t.Fatal(err)
- }
- barAddr := barRes.Header.Get("bar-ipport")
- if barAddr != fooAddr {
- t.Fatalf("/foo came from conn %q; /bar came from %q instead", fooAddr, barAddr)
- }
- barRes.Body.Close()
- dialGate <- false
- }
- // Issue 2184
- func TestTransportReading100Continue(t *testing.T) {
- defer afterTest(t)
- const numReqs = 5
- reqBody := func(n int) string { return fmt.Sprintf("request body %d", n) }
- reqID := func(n int) string { return fmt.Sprintf("REQ-ID-%d", n) }
- send100Response := func(w *io.PipeWriter, r *io.PipeReader) {
- defer w.Close()
- defer r.Close()
- br := bufio.NewReader(r)
- n := 0
- for {
- n++
- req, err := ReadRequest(br)
- if err == io.EOF {
- return
- }
- if err != nil {
- t.Error(err)
- return
- }
- slurp, err := ioutil.ReadAll(req.Body)
- if err != nil {
- t.Errorf("Server request body slurp: %v", err)
- return
- }
- id := req.Header.Get("Request-Id")
- resCode := req.Header.Get("X-Want-Response-Code")
- if resCode == "" {
- resCode = "100 Continue"
- if string(slurp) != reqBody(n) {
- t.Errorf("Server got %q, %v; want %q", slurp, err, reqBody(n))
- }
- }
- body := fmt.Sprintf("Response number %d", n)
- v := []byte(strings.Replace(fmt.Sprintf(`HTTP/1.1 %s
- Date: Thu, 28 Feb 2013 17:55:41 GMT
- HTTP/1.1 200 OK
- Content-Type: text/html
- Echo-Request-Id: %s
- Content-Length: %d
- %s`, resCode, id, len(body), body), "\n", "\r\n", -1))
- w.Write(v)
- if id == reqID(numReqs) {
- return
- }
- }
- }
- tr := &Transport{
- Dial: func(n, addr string) (net.Conn, error) {
- sr, sw := io.Pipe() // server read/write
- cr, cw := io.Pipe() // client read/write
- conn := &rwTestConn{
- Reader: cr,
- Writer: sw,
- closeFunc: func() error {
- sw.Close()
- cw.Close()
- return nil
- },
- }
- go send100Response(cw, sr)
- return conn, nil
- },
- DisableKeepAlives: false,
- }
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- testResponse := func(req *Request, name string, wantCode int) {
- res, err := c.Do(req)
- if err != nil {
- t.Fatalf("%s: Do: %v", name, err)
- }
- if res.StatusCode != wantCode {
- t.Fatalf("%s: Response Statuscode=%d; want %d", name, res.StatusCode, wantCode)
- }
- if id, idBack := req.Header.Get("Request-Id"), res.Header.Get("Echo-Request-Id"); id != "" && id != idBack {
- t.Errorf("%s: response id %q != request id %q", name, idBack, id)
- }
- _, err = ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatalf("%s: Slurp error: %v", name, err)
- }
- }
- // Few 100 responses, making sure we're not off-by-one.
- for i := 1; i <= numReqs; i++ {
- req, _ := NewRequest("POST", "http://dummy.tld/", strings.NewReader(reqBody(i)))
- req.Header.Set("Request-Id", reqID(i))
- testResponse(req, fmt.Sprintf("100, %d/%d", i, numReqs), 200)
- }
- // And some other informational 1xx but non-100 responses, to test
- // we return them but don't re-use the connection.
- for i := 1; i <= numReqs; i++ {
- req, _ := NewRequest("POST", "http://other.tld/", strings.NewReader(reqBody(i)))
- req.Header.Set("X-Want-Response-Code", "123 Sesame Street")
- testResponse(req, fmt.Sprintf("123, %d/%d", i, numReqs), 123)
- }
- }
- type proxyFromEnvTest struct {
- req string // URL to fetch; blank means "http://example.com"
- env string // HTTP_PROXY
- httpsenv string // HTTPS_PROXY
- noenv string // NO_RPXY
- want string
- wanterr error
- }
- func (t proxyFromEnvTest) String() string {
- var buf bytes.Buffer
- space := func() {
- if buf.Len() > 0 {
- buf.WriteByte(' ')
- }
- }
- if t.env != "" {
- fmt.Fprintf(&buf, "http_proxy=%q", t.env)
- }
- if t.httpsenv != "" {
- space()
- fmt.Fprintf(&buf, "https_proxy=%q", t.httpsenv)
- }
- if t.noenv != "" {
- space()
- fmt.Fprintf(&buf, "no_proxy=%q", t.noenv)
- }
- req := "http://example.com"
- if t.req != "" {
- req = t.req
- }
- space()
- fmt.Fprintf(&buf, "req=%q", req)
- return strings.TrimSpace(buf.String())
- }
- var proxyFromEnvTests = []proxyFromEnvTest{
- {env: "127.0.0.1:8080", want: "http://127.0.0.1:8080"},
- {env: "cache.corp.example.com:1234", want: "http://cache.corp.example.com:1234"},
- {env: "cache.corp.example.com", want: "http://cache.corp.example.com"},
- {env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"},
- {env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"},
- {env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"},
- // Don't use secure for http
- {req: "http://insecure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://http.proxy.tld"},
- // Use secure for https.
- {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://secure.proxy.tld"},
- {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "https://secure.proxy.tld", want: "https://secure.proxy.tld"},
- {want: "<nil>"},
- {noenv: "example.com", req: "http://example.com/", env: "proxy", want: "<nil>"},
- {noenv: ".example.com", req: "http://example.com/", env: "proxy", want: "<nil>"},
- {noenv: "ample.com", req: "http://example.com/", env: "proxy", want: "http://proxy"},
- {noenv: "example.com", req: "http://foo.example.com/", env: "proxy", want: "<nil>"},
- {noenv: ".foo.com", req: "http://example.com/", env: "proxy", want: "http://proxy"},
- }
- func TestProxyFromEnvironment(t *testing.T) {
- ResetProxyEnv()
- for _, tt := range proxyFromEnvTests {
- os.Setenv("HTTP_PROXY", tt.env)
- os.Setenv("HTTPS_PROXY", tt.httpsenv)
- os.Setenv("NO_PROXY", tt.noenv)
- ResetCachedEnvironment()
- reqURL := tt.req
- if reqURL == "" {
- reqURL = "http://example.com"
- }
- req, _ := NewRequest("GET", reqURL, nil)
- url, err := ProxyFromEnvironment(req)
- if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e {
- t.Errorf("%v: got error = %q, want %q", tt, g, e)
- continue
- }
- if got := fmt.Sprintf("%s", url); got != tt.want {
- t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want)
- }
- }
- }
- func TestIdleConnChannelLeak(t *testing.T) {
- var mu sync.Mutex
- var n int
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- mu.Lock()
- n++
- mu.Unlock()
- }))
- defer ts.Close()
- const nReqs = 5
- didRead := make(chan bool, nReqs)
- SetReadLoopBeforeNextReadHook(func() { didRead <- true })
- defer SetReadLoopBeforeNextReadHook(nil)
- tr := &Transport{
- Dial: func(netw, addr string) (net.Conn, error) {
- return net.Dial(netw, ts.Listener.Addr().String())
- },
- }
- defer tr.CloseIdleConnections()
- c := &Client{Transport: tr}
- // First, without keep-alives.
- for _, disableKeep := range []bool{true, false} {
- tr.DisableKeepAlives = disableKeep
- for i := 0; i < nReqs; i++ {
- _, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i))
- if err != nil {
- t.Fatal(err)
- }
- // Note: no res.Body.Close is needed here, since the
- // response Content-Length is zero. Perhaps the test
- // should be more explicit and use a HEAD, but tests
- // elsewhere guarantee that zero byte responses generate
- // a "Content-Length: 0" instead of chunking.
- }
- // At this point, each of the 5 Transport.readLoop goroutines
- // are scheduling noting that there are no response bodies (see
- // earlier comment), and are then calling putIdleConn, which
- // decrements this count. Usually that happens quickly, which is
- // why this test has seemed to work for ages. But it's still
- // racey: we have wait for them to finish first. See Issue 10427
- for i := 0; i < nReqs; i++ {
- <-didRead
- }
- if got := tr.IdleConnChMapSizeForTesting(); got != 0 {
- t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got)
- }
- }
- }
- // Verify the status quo: that the Client.Post function coerces its
- // body into a ReadCloser if it's a Closer, and that the Transport
- // then closes it.
- func TestTransportClosesRequestBody(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- io.Copy(ioutil.Discard, r.Body)
- }))
- defer ts.Close()
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- cl := &Client{Transport: tr}
- closes := 0
- res, err := cl.Post(ts.URL, "text/plain", countCloseReader{&closes, strings.NewReader("hello")})
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- if closes != 1 {
- t.Errorf("closes = %d; want 1", closes)
- }
- }
- 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")
- }
- }
- // Tests that we don't leak Transport persistConn.readLoop goroutines
- // when a server hangs up immediately after saying it would keep-alive.
- func TestTransportIssue10457(t *testing.T) {
- defer afterTest(t) // used to fail in goroutine leak check
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- // Send a response with no body, keep-alive
- // (implicit), and then lie and immediately close the
- // connection. This forces the Transport's readLoop to
- // immediately Peek an io.EOF and get to the point
- // that used to hang.
- conn, _, _ := w.(Hijacker).Hijack()
- conn.Write([]byte("HTTP/1.1 200 OK\r\nFoo: Bar\r\nContent-Length: 0\r\n\r\n")) // keep-alive
- conn.Close()
- }))
- defer ts.Close()
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- cl := &Client{Transport: tr}
- res, err := cl.Get(ts.URL)
- if err != nil {
- t.Fatalf("Get: %v", err)
- }
- defer res.Body.Close()
- // Just a sanity check that we at least get the response. The real
- // test here is that the "defer afterTest" above doesn't find any
- // leaked goroutines.
- if got, want := res.Header.Get("Foo"), "Bar"; got != want {
- t.Errorf("Foo header = %q; want %q", got, want)
- }
- }
- 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 https://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 TestTransportDialTLS(t *testing.T) {
- var mu sync.Mutex // guards following
- var gotReq, didDial bool
- ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- mu.Lock()
- gotReq = true
- mu.Unlock()
- }))
- defer ts.Close()
- tr := &Transport{
- DialTLS: func(netw, addr string) (net.Conn, error) {
- mu.Lock()
- didDial = true
- mu.Unlock()
- c, err := tls.Dial(netw, addr, &tls.Config{
- InsecureSkipVerify: true,
- })
- if err != nil {
- return nil, err
- }
- return c, c.Handshake()
- },
- }
- defer tr.CloseIdleConnections()
- client := &Client{Transport: tr}
- res, err := client.Get(ts.URL)
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- mu.Lock()
- if !gotReq {
- t.Error("didn't get request")
- }
- if !didDial {
- t.Error("didn't use dial hook")
- }
- }
- // Test for issue 8755
- // Ensure that if a proxy returns an error, it is exposed by RoundTrip
- func TestRoundTripReturnsProxyError(t *testing.T) {
- badProxy := func(*Request) (*url.URL, error) {
- return nil, errors.New("errorMessage")
- }
- tr := &Transport{Proxy: badProxy}
- req, _ := NewRequest("GET", "http://example.com", nil)
- _, err := tr.RoundTrip(req)
- if err == nil {
- t.Error("Expected proxy error to be returned by RoundTrip")
- }
- }
- // tests that putting an idle conn after a call to CloseIdleConns does return it
- func TestTransportCloseIdleConnsThenReturn(t *testing.T) {
- tr := &Transport{}
- wantIdle := func(when string, n int) bool {
- got := tr.IdleConnCountForTesting("|http|example.com") // key used by PutIdleTestConn
- if got == n {
- return true
- }
- t.Errorf("%s: idle conns = %d; want %d", when, got, n)
- return false
- }
- wantIdle("start", 0)
- if !tr.PutIdleTestConn() {
- t.Fatal("put failed")
- }
- if !tr.PutIdleTestConn() {
- t.Fatal("second put failed")
- }
- wantIdle("after put", 2)
- tr.CloseIdleConnections()
- if !tr.IsIdleForTesting() {
- t.Error("should be idle after CloseIdleConnections")
- }
- wantIdle("after close idle", 0)
- if tr.PutIdleTestConn() {
- t.Fatal("put didn't fail")
- }
- wantIdle("after second put", 0)
- tr.RequestIdleConnChForTesting() // should toggle the transport out of idle mode
- if tr.IsIdleForTesting() {
- t.Error("shouldn't be idle after RequestIdleConnChForTesting")
- }
- if !tr.PutIdleTestConn() {
- t.Fatal("after re-activation")
- }
- wantIdle("after final put", 1)
- }
- // This tests that an client requesting a content range won't also
- // implicitly ask for gzip support. If they want that, they need to do it
- // on their own.
- // golang.org/issue/8923
- func TestTransportRangeAndGzip(t *testing.T) {
- defer afterTest(t)
- reqc := make(chan *Request, 1)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- reqc <- r
- }))
- defer ts.Close()
- req, _ := NewRequest("GET", ts.URL, nil)
- req.Header.Set("Range", "bytes=7-11")
- res, err := DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- select {
- case r := <-reqc:
- if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
- t.Error("Transport advertised gzip support in the Accept header")
- }
- if r.Header.Get("Range") == "" {
- t.Error("no Range in request")
- }
- case <-time.After(10 * time.Second):
- t.Fatal("timeout")
- }
- 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)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- // important that this response has a body.
- var b [1024]byte
- w.Write(b[:])
- }))
- defer ts.Close()
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- req, err := NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- res, err := tr.RoundTrip(req)
- if err != nil {
- t.Fatal(err)
- }
- // If we do an early close, Transport just throws the connection away and
- // doesn't reuse it. In order to trigger the bug, it has to reuse the connection
- // so read the body
- if _, err := io.Copy(ioutil.Discard, res.Body); err != nil {
- t.Fatal(err)
- }
- req2, err := NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- tr.CancelRequest(req)
- res, err = tr.RoundTrip(req2)
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- }
- func TestTransportDialCancelRace(t *testing.T) {
- defer afterTest(t)
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {}))
- defer ts.Close()
- tr := &Transport{}
- defer tr.CloseIdleConnections()
- req, err := NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- SetEnterRoundTripHook(func() {
- tr.CancelRequest(req)
- })
- defer SetEnterRoundTripHook(nil)
- res, err := tr.RoundTrip(req)
- if err != ExportErrRequestCanceled {
- t.Errorf("expected canceled request error; got %v", err)
- if err == nil {
- res.Body.Close()
- }
- }
- }
- // logWritesConn is a net.Conn that logs each Write call to writes
- // and then proxies to w.
- // It proxies Read calls to a reader it receives from rch.
- type logWritesConn struct {
- net.Conn // nil. crash on use.
- w io.Writer
- rch <-chan io.Reader
- r io.Reader // nil until received by rch
- mu sync.Mutex
- writes []string
- }
- func (c *logWritesConn) Write(p []byte) (n int, err error) {
- c.mu.Lock()
- defer c.mu.Unlock()
- c.writes = append(c.writes, string(p))
- return c.w.Write(p)
- }
- func (c *logWritesConn) Read(p []byte) (n int, err error) {
- if c.r == nil {
- c.r = <-c.rch
- }
- return c.r.Read(p)
- }
- func (c *logWritesConn) Close() error { return nil }
- // Issue 6574
- func TestTransportFlushesBodyChunks(t *testing.T) {
- defer afterTest(t)
- resBody := make(chan io.Reader, 1)
- connr, connw := io.Pipe() // connection pipe pair
- lw := &logWritesConn{
- rch: resBody,
- w: connw,
- }
- tr := &Transport{
- Dial: func(network, addr string) (net.Conn, error) {
- return lw, nil
- },
- }
- bodyr, bodyw := io.Pipe() // body pipe pair
- go func() {
- defer bodyw.Close()
- for i := 0; i < 3; i++ {
- fmt.Fprintf(bodyw, "num%d\n", i)
- }
- }()
- resc := make(chan *Response)
- go func() {
- req, _ := NewRequest("POST", "http://localhost:8080", bodyr)
- req.Header.Set("User-Agent", "x") // known value for test
- res, err := tr.RoundTrip(req)
- if err != nil {
- t.Error("RoundTrip: %v", err)
- close(resc)
- return
- }
- resc <- res
- }()
- // Fully consume the request before checking the Write log vs. want.
- req, err := ReadRequest(bufio.NewReader(connr))
- if err != nil {
- t.Fatal(err)
- }
- io.Copy(ioutil.Discard, req.Body)
- // Unblock the transport's roundTrip goroutine.
- resBody <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
- res, ok := <-resc
- if !ok {
- return
- }
- defer res.Body.Close()
- want := []string{
- // Because Request.ContentLength = 0, the body is sniffed for 1 byte to determine whether there's content.
- // That explains the initial "num0" being split into "n" and "um0".
- // The first byte is included with the request headers Write. Perhaps in the future
- // we will want to flush the headers out early if the first byte of the request body is
- // taking a long time to arrive. But not yet.
- "POST / HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: x\r\nTransfer-Encoding: chunked\r\nAccept-Encoding: gzip\r\n\r\n" +
- "1\r\nn\r\n",
- "4\r\num0\n\r\n",
- "5\r\nnum1\n\r\n",
- "5\r\nnum2\n\r\n",
- "0\r\n\r\n",
- }
- if !reflect.DeepEqual(lw.writes, want) {
- t.Errorf("Writes differed.\n Got: %q\nWant: %q\n", lw.writes, want)
- }
- }
- // Issue 11745.
- func TestTransportPrefersResponseOverWriteError(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping in short mode")
- }
- defer afterTest(t)
- const contentLengthLimit = 1024 * 1024 // 1MB
- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
- if r.ContentLength >= contentLengthLimit {
- w.WriteHeader(StatusBadRequest)
- r.Body.Close()
- return
- }
- w.WriteHeader(StatusOK)
- }))
- defer ts.Close()
- fail := 0
- count := 100
- bigBody := strings.Repeat("a", contentLengthLimit*2)
- for i := 0; i < count; i++ {
- req, err := NewRequest("PUT", ts.URL, strings.NewReader(bigBody))
- if err != nil {
- t.Fatal(err)
- }
- tr := new(Transport)
- defer tr.CloseIdleConnections()
- client := &Client{Transport: tr}
- resp, err := client.Do(req)
- if err != nil {
- fail++
- t.Logf("%d = %#v", i, err)
- if ue, ok := err.(*url.Error); ok {
- t.Logf("urlErr = %#v", ue.Err)
- if ne, ok := ue.Err.(*net.OpError); ok {
- t.Logf("netOpError = %#v", ne.Err)
- }
- }
- } else {
- resp.Body.Close()
- if resp.StatusCode != 400 {
- t.Errorf("Expected status code 400, got %v", resp.Status)
- }
- }
- }
- if fail > 0 {
- t.Errorf("Failed %v out of %v\n", fail, count)
- }
- }
- func wantBody(res *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
- }
- func (cr countCloseReader) Close() error {
- (*cr.n)++
- return nil
- }
- // rgz is a gzip quine that uncompresses to itself.
- var rgz = []byte{
- 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73,
- 0x69, 0x76, 0x65, 0x00, 0x92, 0xef, 0xe6, 0xe0,
- 0x60, 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2,
- 0xe2, 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17,
- 0x00, 0xe8, 0xff, 0x92, 0xef, 0xe6, 0xe0, 0x60,
- 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, 0xe2,
- 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, 0x00,
- 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00,
- 0x05, 0x00, 0xfa, 0xff, 0x42, 0x12, 0x46, 0x16,
- 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x05,
- 0x00, 0xfa, 0xff, 0x00, 0x14, 0x00, 0xeb, 0xff,
- 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00,
- 0xfa, 0xff, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00,
- 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4,
- 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88,
- 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff,
- 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00,
- 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00,
- 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4,
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
- 0x00, 0xff, 0xff, 0x00, 0x17, 0x00, 0xe8, 0xff,
- 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
- 0x17, 0x00, 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16,
- 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08,
- 0x00, 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa,
- 0x00, 0x00, 0x00, 0x42, 0x12, 0x46, 0x16, 0x06,
- 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x00,
- 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00,
- 0x00, 0x00, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00,
- 0x00, 0x00,
- }