1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Tests for transport.go
27 // TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close
28 // and then verify that the final 2 responses get errors back.
30 // hostPortHandler writes back the client's "host:port".
31 var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
32 if r.FormValue("close") == "true" {
33 w.Header().Set("Connection", "close")
35 w.Write([]byte(r.RemoteAddr))
38 // Two subsequent requests and verify their response is the same.
39 // The response from the server is our own IP:port
40 func TestTransportKeepAlives(t *testing.T) {
41 ts := httptest.NewServer(hostPortHandler)
44 for _, disableKeepAlive := range []bool{false, true} {
45 tr := &Transport{DisableKeepAlives: disableKeepAlive}
46 c := &Client{Transport: tr}
48 fetch := func(n int) string {
49 res, err := c.Get(ts.URL)
51 t.Fatalf("error in disableKeepAlive=%v, req #%d, GET: %v", disableKeepAlive, n, err)
53 body, err := ioutil.ReadAll(res.Body)
55 t.Fatalf("error in disableKeepAlive=%v, req #%d, ReadAll: %v", disableKeepAlive, n, err)
63 bodiesDiffer := body1 != body2
64 if bodiesDiffer != disableKeepAlive {
65 t.Errorf("error in disableKeepAlive=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
66 disableKeepAlive, bodiesDiffer, body1, body2)
71 func TestTransportConnectionCloseOnResponse(t *testing.T) {
72 ts := httptest.NewServer(hostPortHandler)
75 for _, connectionClose := range []bool{false, true} {
77 c := &Client{Transport: tr}
79 fetch := func(n int) string {
82 req.URL, err = url.Parse(ts.URL + fmt.Sprintf("/?close=%v", connectionClose))
84 t.Fatalf("URL parse error: %v", err)
87 req.Proto = "HTTP/1.1"
93 t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err)
95 body, err := ioutil.ReadAll(res.Body)
96 defer res.Body.Close()
98 t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err)
105 bodiesDiffer := body1 != body2
106 if bodiesDiffer != connectionClose {
107 t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
108 connectionClose, bodiesDiffer, body1, body2)
113 func TestTransportConnectionCloseOnRequest(t *testing.T) {
114 ts := httptest.NewServer(hostPortHandler)
117 for _, connectionClose := range []bool{false, true} {
119 c := &Client{Transport: tr}
121 fetch := func(n int) string {
124 req.URL, err = url.Parse(ts.URL)
126 t.Fatalf("URL parse error: %v", err)
129 req.Proto = "HTTP/1.1"
132 req.Close = connectionClose
134 res, err := c.Do(req)
136 t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err)
138 body, err := ioutil.ReadAll(res.Body)
140 t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err)
147 bodiesDiffer := body1 != body2
148 if bodiesDiffer != connectionClose {
149 t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q",
150 connectionClose, bodiesDiffer, body1, body2)
155 func TestTransportIdleCacheKeys(t *testing.T) {
156 ts := httptest.NewServer(hostPortHandler)
159 tr := &Transport{DisableKeepAlives: false}
160 c := &Client{Transport: tr}
162 if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
163 t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g)
166 resp, err := c.Get(ts.URL)
170 ioutil.ReadAll(resp.Body)
172 keys := tr.IdleConnKeysForTesting()
173 if e, g := 1, len(keys); e != g {
174 t.Fatalf("After Get expected %d idle conn cache keys; got %d", e, g)
177 if e := "|http|" + ts.Listener.Addr().String(); keys[0] != e {
178 t.Errorf("Expected idle cache key %q; got %q", e, keys[0])
181 tr.CloseIdleConnections()
182 if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
183 t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g)
187 func TestTransportMaxPerHostIdleConns(t *testing.T) {
188 resch := make(chan string)
189 gotReq := make(chan bool)
190 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
193 _, err := w.Write([]byte(msg))
195 t.Fatalf("Write: %v", err)
200 tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns}
201 c := &Client{Transport: tr}
203 // Start 3 outstanding requests and wait for the server to get them.
204 // Their responses will hang until we we write to resch, though.
205 donech := make(chan bool)
207 resp, err := c.Get(ts.URL)
211 _, err = ioutil.ReadAll(resp.Body)
213 t.Fatalf("ReadAll: %v", err)
224 if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
225 t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g)
230 keys := tr.IdleConnKeysForTesting()
231 if e, g := 1, len(keys); e != g {
232 t.Fatalf("after first response, expected %d idle conn cache keys; got %d", e, g)
234 cacheKey := "|http|" + ts.Listener.Addr().String()
235 if keys[0] != cacheKey {
236 t.Fatalf("Expected idle cache key %q; got %q", cacheKey, keys[0])
238 if e, g := 1, tr.IdleConnCountForTesting(cacheKey); e != g {
239 t.Errorf("after first response, expected %d idle conns; got %d", e, g)
244 if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g {
245 t.Errorf("after second response, expected %d idle conns; got %d", e, g)
250 if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g {
251 t.Errorf("after third response, still expected %d idle conns; got %d", e, g)
255 func TestTransportServerClosingUnexpectedly(t *testing.T) {
256 ts := httptest.NewServer(hostPortHandler)
260 c := &Client{Transport: tr}
262 fetch := func(n, retries int) string {
263 condFatalf := func(format string, arg ...interface{}) {
265 t.Fatalf(format, arg...)
267 t.Logf("retrying shortly after expected error: "+format, arg...)
268 time.Sleep(time.Second / time.Duration(retries))
272 res, err := c.Get(ts.URL)
274 condFatalf("error in req #%d, GET: %v", n, err)
277 body, err := ioutil.ReadAll(res.Body)
279 condFatalf("error in req #%d, ReadAll: %v", n, err)
291 ts.CloseClientConnections() // surprise!
293 // This test has an expected race. Sleeping for 25 ms prevents
294 // it on most fast machines, causing the next fetch() call to
295 // succeed quickly. But if we do get errors, fetch() will retry 5
296 // times with some delays between.
297 time.Sleep(25 * time.Millisecond)
302 t.Errorf("expected body1 and body2 to be equal")
305 t.Errorf("expected body2 and body3 to be different")
309 // Test for http://golang.org/issue/2616 (appropriate issue number)
310 // This fails pretty reliably with GOMAXPROCS=100 or something high.
311 func TestStressSurpriseServerCloses(t *testing.T) {
313 t.Logf("skipping test in short mode")
316 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
317 w.Header().Set("Content-Length", "5")
318 w.Header().Set("Content-Type", "text/plain")
319 w.Write([]byte("Hello"))
321 conn, buf, _ := w.(Hijacker).Hijack()
327 tr := &Transport{DisableKeepAlives: false}
328 c := &Client{Transport: tr}
330 // Do a bunch of traffic from different goroutines. Send to activityc
331 // after each request completes, regardless of whether it failed.
336 activityc := make(chan bool)
337 for i := 0; i < numClients; i++ {
339 for i := 0; i < reqsPerClient; i++ {
340 res, err := c.Get(ts.URL)
342 // We expect errors since the server is
343 // hanging up on us after telling us to
344 // send more requests, so we don't
345 // actually care what the error is.
346 // But we want to close the body in cases
347 // where we won the race.
355 // Make sure all the request come back, one way or another.
356 for i := 0; i < numClients*reqsPerClient; i++ {
359 case <-time.After(5 * time.Second):
360 t.Fatalf("presumed deadlock; no HTTP client activity seen in awhile")
365 // TestTransportHeadResponses verifies that we deal with Content-Lengths
366 // with no bodies properly
367 func TestTransportHeadResponses(t *testing.T) {
368 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
369 if r.Method != "HEAD" {
370 panic("expected HEAD; got " + r.Method)
372 w.Header().Set("Content-Length", "123")
377 tr := &Transport{DisableKeepAlives: false}
378 c := &Client{Transport: tr}
379 for i := 0; i < 2; i++ {
380 res, err := c.Head(ts.URL)
382 t.Errorf("error on loop %d: %v", i, err)
384 if e, g := "123", res.Header.Get("Content-Length"); e != g {
385 t.Errorf("loop %d: expected Content-Length header of %q, got %q", i, e, g)
387 if e, g := int64(0), res.ContentLength; e != g {
388 t.Errorf("loop %d: expected res.ContentLength of %v, got %v", i, e, g)
393 // TestTransportHeadChunkedResponse verifies that we ignore chunked transfer-encoding
394 // on responses to HEAD requests.
395 func TestTransportHeadChunkedResponse(t *testing.T) {
396 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
397 if r.Method != "HEAD" {
398 panic("expected HEAD; got " + r.Method)
400 w.Header().Set("Transfer-Encoding", "chunked") // client should ignore
401 w.Header().Set("x-client-ipport", r.RemoteAddr)
406 tr := &Transport{DisableKeepAlives: false}
407 c := &Client{Transport: tr}
409 res1, err := c.Head(ts.URL)
411 t.Fatalf("request 1 error: %v", err)
413 res2, err := c.Head(ts.URL)
415 t.Fatalf("request 2 error: %v", err)
417 if v1, v2 := res1.Header.Get("x-client-ipport"), res2.Header.Get("x-client-ipport"); v1 != v2 {
418 t.Errorf("ip/ports differed between head requests: %q vs %q", v1, v2)
422 var roundTripTests = []struct {
427 // Requests with no accept-encoding header use transparent compression
429 // Requests with other accept-encoding should pass through unmodified
430 {"foo", "foo", false},
431 // Requests with accept-encoding == gzip should be passed through
432 {"gzip", "gzip", true},
435 // Test that the modification made to the Request by the RoundTripper is cleaned up
436 func TestRoundTripGzip(t *testing.T) {
437 const responseBody = "test response body"
438 ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
439 accept := req.Header.Get("Accept-Encoding")
440 if expect := req.FormValue("expect_accept"); accept != expect {
441 t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",
442 req.FormValue("testnum"), accept, expect)
444 if accept == "gzip" {
445 rw.Header().Set("Content-Encoding", "gzip")
446 gz := gzip.NewWriter(rw)
447 gz.Write([]byte(responseBody))
450 rw.Header().Set("Content-Encoding", accept)
451 rw.Write([]byte(responseBody))
456 for i, test := range roundTripTests {
457 // Test basic request (no accept-encoding)
458 req, _ := NewRequest("GET", fmt.Sprintf("%s/?testnum=%d&expect_accept=%s", ts.URL, i, test.expectAccept), nil)
459 if test.accept != "" {
460 req.Header.Set("Accept-Encoding", test.accept)
462 res, err := DefaultTransport.RoundTrip(req)
465 gzip, err := gzip.NewReader(res.Body)
467 t.Errorf("%d. gzip NewReader: %v", i, err)
470 body, err = ioutil.ReadAll(gzip)
473 body, err = ioutil.ReadAll(res.Body)
476 t.Errorf("%d. Error: %q", i, err)
479 if g, e := string(body), responseBody; g != e {
480 t.Errorf("%d. body = %q; want %q", i, g, e)
482 if g, e := req.Header.Get("Accept-Encoding"), test.accept; g != e {
483 t.Errorf("%d. Accept-Encoding = %q; want %q (it was mutated, in violation of RoundTrip contract)", i, g, e)
485 if g, e := res.Header.Get("Content-Encoding"), test.accept; g != e {
486 t.Errorf("%d. Content-Encoding = %q; want %q", i, g, e)
492 func TestTransportGzip(t *testing.T) {
493 const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
494 const nRandBytes = 1024 * 1024
495 ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
496 if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e {
497 t.Errorf("Accept-Encoding = %q, want %q", g, e)
499 rw.Header().Set("Content-Encoding", "gzip")
500 if req.Method == "HEAD" {
506 if req.FormValue("chunked") == "0" {
508 defer io.Copy(rw, &buf)
510 rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
513 gz := gzip.NewWriter(w)
514 gz.Write([]byte(testString))
515 if req.FormValue("body") == "large" {
516 io.CopyN(gz, rand.Reader, nRandBytes)
522 for _, chunked := range []string{"1", "0"} {
523 c := &Client{Transport: &Transport{}}
525 // First fetch something large, but only read some of it.
526 res, err := c.Get(ts.URL + "/?body=large&chunked=" + chunked)
528 t.Fatalf("large get: %v", err)
530 buf := make([]byte, len(testString))
531 n, err := io.ReadFull(res.Body, buf)
533 t.Fatalf("partial read of large response: size=%d, %v", n, err)
535 if e, g := testString, string(buf); e != g {
536 t.Errorf("partial read got %q, expected %q", g, e)
539 // Read on the body, even though it's closed
540 n, err = res.Body.Read(buf)
541 if n != 0 || err == nil {
542 t.Errorf("expected error post-closed large Read; got = %d, %v", n, err)
545 // Then something small.
546 res, err = c.Get(ts.URL + "/?chunked=" + chunked)
550 body, err := ioutil.ReadAll(res.Body)
554 if g, e := string(body), testString; g != e {
555 t.Fatalf("body = %q; want %q", g, e)
557 if g, e := res.Header.Get("Content-Encoding"), ""; g != e {
558 t.Fatalf("Content-Encoding = %q; want %q", g, e)
561 // Read on the body after it's been fully read:
562 n, err = res.Body.Read(buf)
563 if n != 0 || err == nil {
564 t.Errorf("expected Read error after exhausted reads; got %d, %v", n, err)
567 n, err = res.Body.Read(buf)
568 if n != 0 || err == nil {
569 t.Errorf("expected Read error after Close; got %d, %v", n, err)
573 // And a HEAD request too, because they're always weird.
574 c := &Client{Transport: &Transport{}}
575 res, err := c.Head(ts.URL)
577 t.Fatalf("Head: %v", err)
579 if res.StatusCode != 200 {
580 t.Errorf("Head status=%d; want=200", res.StatusCode)
584 func TestTransportProxy(t *testing.T) {
585 ch := make(chan string, 1)
586 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
590 proxy := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
591 ch <- "proxy for " + r.URL.String()
595 pu, err := url.Parse(proxy.URL)
599 c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}}
602 want := "proxy for " + ts.URL + "/"
604 t.Errorf("want %q, got %q", want, got)
608 // TestTransportGzipRecursive sends a gzip quine and checks that the
609 // client gets the same value back. This is more cute than anything,
610 // but checks that we don't recurse forever, and checks that
611 // Content-Encoding is removed.
612 func TestTransportGzipRecursive(t *testing.T) {
613 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
614 w.Header().Set("Content-Encoding", "gzip")
619 c := &Client{Transport: &Transport{}}
620 res, err := c.Get(ts.URL)
624 body, err := ioutil.ReadAll(res.Body)
628 if !bytes.Equal(body, rgz) {
629 t.Fatalf("Incorrect result from recursive gz:\nhave=%x\nwant=%x",
632 if g, e := res.Header.Get("Content-Encoding"), ""; g != e {
633 t.Fatalf("Content-Encoding = %q; want %q", g, e)
637 // tests that persistent goroutine connections shut down when no longer desired.
638 func TestTransportPersistConnLeak(t *testing.T) {
639 gotReqCh := make(chan bool)
640 unblockCh := make(chan bool)
641 ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
644 w.Header().Set("Content-Length", "0")
650 c := &Client{Transport: tr}
652 n0 := runtime.NumGoroutine()
655 didReqCh := make(chan bool)
656 for i := 0; i < numReq; i++ {
658 res, err := c.Get(ts.URL)
661 t.Errorf("client fetch error: %v", err)
668 // Wait for all goroutines to be stuck in the Handler.
669 for i := 0; i < numReq; i++ {
673 nhigh := runtime.NumGoroutine()
675 // Tell all handlers to unblock and reply.
676 for i := 0; i < numReq; i++ {
680 // Wait for all HTTP clients to be done.
681 for i := 0; i < numReq; i++ {
685 tr.CloseIdleConnections()
686 time.Sleep(100 * time.Millisecond)
688 runtime.GC() // even more.
689 nfinal := runtime.NumGoroutine()
691 growth := nfinal - n0
693 // We expect 0 or 1 extra goroutine, empirically. Allow up to 5.
694 // Previously we were leaking one per numReq.
695 t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
697 t.Error("too many new goroutines")
701 type fooProto struct{}
703 func (fooProto) RoundTrip(req *Request) (*Response, error) {
707 Header: make(Header),
708 Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())),
713 func TestTransportAltProto(t *testing.T) {
715 c := &Client{Transport: tr}
716 tr.RegisterProtocol("foo", fooProto{})
717 res, err := c.Get("foo://bar.com/path")
721 bodyb, err := ioutil.ReadAll(res.Body)
725 body := string(bodyb)
726 if e := "You wanted foo://bar.com/path"; body != e {
727 t.Errorf("got response %q, want %q", body, e)
731 var proxyFromEnvTests = []struct {
736 {"127.0.0.1:8080", "http://127.0.0.1:8080", nil},
737 {"http://127.0.0.1:8080", "http://127.0.0.1:8080", nil},
738 {"https://127.0.0.1:8080", "https://127.0.0.1:8080", nil},
742 func TestProxyFromEnvironment(t *testing.T) {
743 os.Setenv("HTTP_PROXY", "")
744 os.Setenv("http_proxy", "")
745 os.Setenv("NO_PROXY", "")
746 os.Setenv("no_proxy", "")
747 for i, tt := range proxyFromEnvTests {
748 os.Setenv("HTTP_PROXY", tt.env)
749 req, _ := NewRequest("GET", "http://example.com", nil)
750 url, err := ProxyFromEnvironment(req)
751 if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e {
752 t.Errorf("%d. got error = %q, want %q", i, g, e)
755 if got := fmt.Sprintf("%s", url); got != tt.wanturl {
756 t.Errorf("%d. got URL = %q, want %q", i, url, tt.wanturl)
761 // rgz is a gzip quine that uncompresses to itself.
763 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
764 0x00, 0x00, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73,
765 0x69, 0x76, 0x65, 0x00, 0x92, 0xef, 0xe6, 0xe0,
766 0x60, 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2,
767 0xe2, 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17,
768 0x00, 0xe8, 0xff, 0x92, 0xef, 0xe6, 0xe0, 0x60,
769 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, 0xe2,
770 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, 0x00,
771 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00,
772 0x05, 0x00, 0xfa, 0xff, 0x42, 0x12, 0x46, 0x16,
773 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x05,
774 0x00, 0xfa, 0xff, 0x00, 0x14, 0x00, 0xeb, 0xff,
775 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00,
776 0xfa, 0xff, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00,
777 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4,
778 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88,
779 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff,
780 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00,
781 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00,
782 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4,
783 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
784 0x00, 0xff, 0xff, 0x00, 0x17, 0x00, 0xe8, 0xff,
785 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x00, 0x00,
786 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
787 0x17, 0x00, 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16,
788 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08,
789 0x00, 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa,
790 0x00, 0x00, 0x00, 0x42, 0x12, 0x46, 0x16, 0x06,
791 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x00,
792 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00,
793 0x00, 0x00, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00,