2011-03-11 10:54:31 -07:00
|
|
|
// 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 (
|
2011-04-12 10:35:07 -06:00
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
2011-04-27 15:23:25 -06:00
|
|
|
"crypto/rand"
|
2011-03-11 10:54:31 -07:00
|
|
|
"fmt"
|
2011-04-27 15:23:25 -06:00
|
|
|
"io"
|
2011-03-11 10:54:31 -07:00
|
|
|
"io/ioutil"
|
2012-05-18 11:34:37 -06:00
|
|
|
"net"
|
2011-11-08 16:41:54 -07:00
|
|
|
. "net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
2012-02-29 10:52:52 -07:00
|
|
|
"os"
|
2012-02-13 18:48:56 -07:00
|
|
|
"runtime"
|
2011-04-27 15:23:25 -06:00
|
|
|
"strconv"
|
2011-05-25 13:31:11 -06:00
|
|
|
"strings"
|
2012-05-18 11:34:37 -06:00
|
|
|
"sync"
|
2011-03-11 10:54:31 -07:00
|
|
|
"testing"
|
2011-03-23 11:38:18 -06:00
|
|
|
"time"
|
2011-03-11 10:54:31 -07:00
|
|
|
)
|
|
|
|
|
2011-03-23 11:38:18 -06:00
|
|
|
// 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")
|
|
|
|
}
|
2011-04-12 10:35:07 -06:00
|
|
|
w.Write([]byte(r.RemoteAddr))
|
2011-03-23 11:38:18 -06:00
|
|
|
})
|
|
|
|
|
2012-05-23 12:19:38 -06:00
|
|
|
// testCloseConn is a net.Conn tracked by a testConnSet.
|
2012-05-18 11:34:37 -06:00
|
|
|
type testCloseConn struct {
|
|
|
|
net.Conn
|
|
|
|
set *testConnSet
|
|
|
|
}
|
|
|
|
|
2012-05-23 12:19:38 -06:00
|
|
|
func (c *testCloseConn) Close() error {
|
|
|
|
c.set.remove(c)
|
|
|
|
return c.Conn.Close()
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
|
2012-05-23 12:19:38 -06:00
|
|
|
// testConnSet tracks a set of TCP connections and whether they've
|
|
|
|
// been closed.
|
2012-05-18 11:34:37 -06:00
|
|
|
type testConnSet struct {
|
2012-05-23 12:19:38 -06:00
|
|
|
t *testing.T
|
2012-05-21 11:39:31 -06:00
|
|
|
closed map[net.Conn]bool
|
|
|
|
list []net.Conn // in order created
|
|
|
|
mutex sync.Mutex
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (tcs *testConnSet) insert(c net.Conn) {
|
|
|
|
tcs.mutex.Lock()
|
|
|
|
defer tcs.mutex.Unlock()
|
2012-05-21 11:39:31 -06:00
|
|
|
tcs.closed[c] = false
|
|
|
|
tcs.list = append(tcs.list, c)
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (tcs *testConnSet) remove(c net.Conn) {
|
|
|
|
tcs.mutex.Lock()
|
|
|
|
defer tcs.mutex.Unlock()
|
2012-05-21 11:39:31 -06:00
|
|
|
tcs.closed[c] = true
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// some tests use this to manage raw tcp connections for later inspection
|
2012-05-23 12:19:38 -06:00
|
|
|
func makeTestDial(t *testing.T) (*testConnSet, func(n, addr string) (net.Conn, error)) {
|
2012-05-18 11:34:37 -06:00
|
|
|
connSet := &testConnSet{
|
2012-05-23 12:19:38 -06:00
|
|
|
t: t,
|
2012-05-21 11:39:31 -06:00
|
|
|
closed: make(map[net.Conn]bool),
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2012-05-21 11:39:31 -06:00
|
|
|
func (tcs *testConnSet) check(t *testing.T) {
|
2012-05-18 11:34:37 -06:00
|
|
|
tcs.mutex.Lock()
|
|
|
|
defer tcs.mutex.Unlock()
|
|
|
|
|
2012-05-21 11:39:31 -06:00
|
|
|
for i, c := range tcs.list {
|
|
|
|
if !tcs.closed[c] {
|
2012-05-23 12:19:38 -06:00
|
|
|
t.Errorf("TCP connection #%d, %p (of %d total) was not closed", i+1, c, len(tcs.list))
|
2012-05-18 11:34:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-23 11:38:18 -06:00
|
|
|
// 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) {
|
|
|
|
ts := httptest.NewServer(hostPortHandler)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
for _, disableKeepAlive := range []bool{false, true} {
|
|
|
|
tr := &Transport{DisableKeepAlives: disableKeepAlive}
|
|
|
|
c := &Client{Transport: tr}
|
|
|
|
|
|
|
|
fetch := func(n int) string {
|
2011-05-13 08:31:24 -06:00
|
|
|
res, err := c.Get(ts.URL)
|
2011-03-23 11:38:18 -06:00
|
|
|
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) {
|
|
|
|
ts := httptest.NewServer(hostPortHandler)
|
|
|
|
defer ts.Close()
|
|
|
|
|
2012-05-23 12:19:38 -06:00
|
|
|
connSet, testDial := makeTestDial(t)
|
2012-05-18 11:34:37 -06:00
|
|
|
|
2011-03-23 11:38:18 -06:00
|
|
|
for _, connectionClose := range []bool{false, true} {
|
2012-05-18 11:34:37 -06:00
|
|
|
tr := &Transport{
|
|
|
|
Dial: testDial,
|
|
|
|
}
|
2011-03-23 11:38:18 -06:00
|
|
|
c := &Client{Transport: tr}
|
|
|
|
|
|
|
|
fetch := func(n int) string {
|
|
|
|
req := new(Request)
|
2011-11-01 20:04:37 -06:00
|
|
|
var err error
|
2011-10-12 12:48:25 -06:00
|
|
|
req.URL, err = url.Parse(ts.URL + fmt.Sprintf("/?close=%v", connectionClose))
|
2011-03-23 11:38:18 -06:00
|
|
|
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)
|
|
|
|
}
|
2012-08-20 19:46:07 -06:00
|
|
|
defer res.Body.Close()
|
2011-03-23 11:38:18 -06:00
|
|
|
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)
|
|
|
|
}
|
2012-05-18 11:34:37 -06:00
|
|
|
|
|
|
|
tr.CloseIdleConnections()
|
|
|
|
}
|
|
|
|
|
2012-05-21 11:39:31 -06:00
|
|
|
connSet.check(t)
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestTransportConnectionCloseOnRequest(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(hostPortHandler)
|
|
|
|
defer ts.Close()
|
|
|
|
|
2012-05-23 12:19:38 -06:00
|
|
|
connSet, testDial := makeTestDial(t)
|
2012-05-18 11:34:37 -06:00
|
|
|
|
2011-03-23 11:38:18 -06:00
|
|
|
for _, connectionClose := range []bool{false, true} {
|
2012-05-18 11:34:37 -06:00
|
|
|
tr := &Transport{
|
|
|
|
Dial: testDial,
|
|
|
|
}
|
2011-03-23 11:38:18 -06:00
|
|
|
c := &Client{Transport: tr}
|
|
|
|
|
|
|
|
fetch := func(n int) string {
|
|
|
|
req := new(Request)
|
2011-11-01 20:04:37 -06:00
|
|
|
var err error
|
2011-08-16 21:36:02 -06:00
|
|
|
req.URL, err = url.Parse(ts.URL)
|
2011-03-23 11:38:18 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2012-05-18 11:34:37 -06:00
|
|
|
|
|
|
|
tr.CloseIdleConnections()
|
|
|
|
}
|
|
|
|
|
2012-05-21 11:39:31 -06:00
|
|
|
connSet.check(t)
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestTransportIdleCacheKeys(t *testing.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)
|
|
|
|
}
|
|
|
|
|
2011-05-13 08:31:24 -06:00
|
|
|
resp, err := c.Get(ts.URL)
|
2011-04-04 20:22:47 -06:00
|
|
|
if err != nil {
|
2011-03-23 11:38:18 -06:00
|
|
|
t.Error(err)
|
|
|
|
}
|
2011-04-04 20:22:47 -06:00
|
|
|
ioutil.ReadAll(resp.Body)
|
2011-03-23 11:38:18 -06:00
|
|
|
|
|
|
|
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 {
|
2011-03-31 13:58:50 -06:00
|
|
|
t.Errorf("Expected idle cache key %q; got %q", e, keys[0])
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-31 13:58:50 -06:00
|
|
|
func TestTransportMaxPerHostIdleConns(t *testing.T) {
|
2011-04-14 10:07:20 -06:00
|
|
|
resch := make(chan string)
|
|
|
|
gotReq := make(chan bool)
|
2011-03-31 13:58:50 -06:00
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
2011-04-14 10:07:20 -06:00
|
|
|
gotReq <- true
|
|
|
|
msg := <-resch
|
|
|
|
_, err := w.Write([]byte(msg))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Write: %v", err)
|
|
|
|
}
|
2011-03-31 13:58:50 -06:00
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
maxIdleConns := 2
|
|
|
|
tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns}
|
|
|
|
c := &Client{Transport: tr}
|
|
|
|
|
2011-04-14 10:07:20 -06:00
|
|
|
// Start 3 outstanding requests and wait for the server to get them.
|
2012-11-21 11:58:24 -07:00
|
|
|
// Their responses will hang until we write to resch, though.
|
2011-03-31 13:58:50 -06:00
|
|
|
donech := make(chan bool)
|
|
|
|
doReq := func() {
|
2011-05-13 08:31:24 -06:00
|
|
|
resp, err := c.Get(ts.URL)
|
2011-04-04 20:22:47 -06:00
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
2011-04-14 10:07:20 -06:00
|
|
|
_, err = ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ReadAll: %v", err)
|
|
|
|
}
|
2011-03-31 13:58:50 -06:00
|
|
|
donech <- true
|
|
|
|
}
|
|
|
|
go doReq()
|
2011-04-14 10:07:20 -06:00
|
|
|
<-gotReq
|
2011-03-31 13:58:50 -06:00
|
|
|
go doReq()
|
2011-04-14 10:07:20 -06:00
|
|
|
<-gotReq
|
2011-03-31 13:58:50 -06:00
|
|
|
go doReq()
|
2011-04-14 10:07:20 -06:00
|
|
|
<-gotReq
|
2011-03-31 13:58:50 -06:00
|
|
|
|
|
|
|
if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g {
|
|
|
|
t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g)
|
|
|
|
}
|
|
|
|
|
2011-04-14 10:07:20 -06:00
|
|
|
resch <- "res1"
|
2011-03-31 13:58:50 -06:00
|
|
|
<-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)
|
|
|
|
}
|
|
|
|
|
2011-04-14 10:07:20 -06:00
|
|
|
resch <- "res2"
|
2011-03-31 13:58:50 -06:00
|
|
|
<-donech
|
|
|
|
if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g {
|
|
|
|
t.Errorf("after second response, expected %d idle conns; got %d", e, g)
|
|
|
|
}
|
|
|
|
|
2011-04-14 10:07:20 -06:00
|
|
|
resch <- "res3"
|
2011-03-31 13:58:50 -06:00
|
|
|
<-donech
|
|
|
|
if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g {
|
|
|
|
t.Errorf("after third response, still expected %d idle conns; got %d", e, g)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-23 11:38:18 -06:00
|
|
|
func TestTransportServerClosingUnexpectedly(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(hostPortHandler)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
tr := &Transport{}
|
|
|
|
c := &Client{Transport: tr}
|
|
|
|
|
2011-05-03 12:25:35 -06:00
|
|
|
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...)
|
2011-11-30 10:01:46 -07:00
|
|
|
time.Sleep(time.Second / time.Duration(retries))
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
2011-05-03 12:25:35 -06:00
|
|
|
for retries >= 0 {
|
|
|
|
retries--
|
2011-05-13 08:31:24 -06:00
|
|
|
res, err := c.Get(ts.URL)
|
2011-05-03 12:25:35 -06:00
|
|
|
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)
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
2011-05-03 12:25:35 -06:00
|
|
|
panic("unreachable")
|
2011-03-23 11:38:18 -06:00
|
|
|
}
|
|
|
|
|
2011-05-03 12:25:35 -06:00
|
|
|
body1 := fetch(1, 0)
|
|
|
|
body2 := fetch(2, 0)
|
2011-03-23 11:38:18 -06:00
|
|
|
|
|
|
|
ts.CloseClientConnections() // surprise!
|
|
|
|
|
2011-05-03 12:25:35 -06:00
|
|
|
// 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.
|
2011-12-12 16:42:56 -07:00
|
|
|
time.Sleep(25 * time.Millisecond)
|
2011-05-03 12:25:35 -06:00
|
|
|
|
|
|
|
body3 := fetch(3, 5)
|
2011-03-23 11:38:18 -06:00
|
|
|
|
|
|
|
if body1 != body2 {
|
|
|
|
t.Errorf("expected body1 and body2 to be equal")
|
|
|
|
}
|
|
|
|
if body2 == body3 {
|
|
|
|
t.Errorf("expected body2 and body3 to be different")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-01-25 13:31:06 -07:00
|
|
|
// Test for http://golang.org/issue/2616 (appropriate issue number)
|
|
|
|
// This fails pretty reliably with GOMAXPROCS=100 or something high.
|
|
|
|
func TestStressSurpriseServerCloses(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Logf("skipping test in short mode")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
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}
|
|
|
|
|
|
|
|
// Do a bunch of traffic from different goroutines. Send to activityc
|
|
|
|
// after each request completes, regardless of whether it failed.
|
|
|
|
const (
|
|
|
|
numClients = 50
|
|
|
|
reqsPerClient = 250
|
|
|
|
)
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-04 17:58:11 -06:00
|
|
|
// TestTransportHeadResponses verifies that we deal with Content-Lengths
|
|
|
|
// with no bodies properly
|
|
|
|
func TestTransportHeadResponses(t *testing.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)
|
|
|
|
}
|
|
|
|
if e, g := "123", res.Header.Get("Content-Length"); e != g {
|
2011-04-11 14:05:08 -06:00
|
|
|
t.Errorf("loop %d: expected Content-Length header of %q, got %q", i, e, g)
|
2011-04-04 17:58:11 -06:00
|
|
|
}
|
2012-12-05 23:36:23 -07:00
|
|
|
if e, g := int64(123), res.ContentLength; e != g {
|
2011-04-11 14:05:08 -06:00
|
|
|
t.Errorf("loop %d: expected res.ContentLength of %v, got %v", i, e, g)
|
2011-04-04 17:58:11 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-04 20:43:36 -06:00
|
|
|
// TestTransportHeadChunkedResponse verifies that we ignore chunked transfer-encoding
|
|
|
|
// on responses to HEAD requests.
|
|
|
|
func TestTransportHeadChunkedResponse(t *testing.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}
|
|
|
|
|
|
|
|
res1, err := c.Head(ts.URL)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("request 1 error: %v", err)
|
|
|
|
}
|
|
|
|
res2, err := c.Head(ts.URL)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-10 15:16:13 -06:00
|
|
|
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
|
2011-10-14 15:16:43 -06:00
|
|
|
{"gzip", "gzip", true},
|
|
|
|
}
|
2011-08-10 15:16:13 -06:00
|
|
|
|
|
|
|
// Test that the modification made to the Request by the RoundTripper is cleaned up
|
|
|
|
func TestRoundTripGzip(t *testing.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 {
|
2011-10-14 15:16:43 -06:00
|
|
|
t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",
|
|
|
|
req.FormValue("testnum"), accept, expect)
|
2011-08-10 15:16:13 -06:00
|
|
|
}
|
|
|
|
if accept == "gzip" {
|
|
|
|
rw.Header().Set("Content-Encoding", "gzip")
|
2012-02-10 00:49:19 -07:00
|
|
|
gz := gzip.NewWriter(rw)
|
2011-08-10 15:16:13 -06:00
|
|
|
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)
|
2011-10-14 15:16:43 -06:00
|
|
|
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)
|
|
|
|
}
|
2011-08-10 15:16:13 -06:00
|
|
|
res, err := DefaultTransport.RoundTrip(req)
|
|
|
|
var body []byte
|
|
|
|
if test.compressed {
|
2012-02-07 20:15:25 -07:00
|
|
|
gzip, err := gzip.NewReader(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%d. gzip NewReader: %v", i, err)
|
|
|
|
continue
|
|
|
|
}
|
2011-08-10 15:16:13 -06:00
|
|
|
body, err = ioutil.ReadAll(gzip)
|
|
|
|
res.Body.Close()
|
|
|
|
} else {
|
|
|
|
body, err = ioutil.ReadAll(res.Body)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%d. Error: %q", i, err)
|
2011-10-14 15:16:43 -06:00
|
|
|
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)
|
2011-08-10 15:16:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2011-04-12 10:35:07 -06:00
|
|
|
func TestTransportGzip(t *testing.T) {
|
|
|
|
const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
2011-04-27 15:23:25 -06:00
|
|
|
const nRandBytes = 1024 * 1024
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
|
|
|
|
if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e {
|
2011-04-12 10:35:07 -06:00
|
|
|
t.Errorf("Accept-Encoding = %q, want %q", g, e)
|
|
|
|
}
|
2011-04-27 15:23:25 -06:00
|
|
|
rw.Header().Set("Content-Encoding", "gzip")
|
2011-05-11 20:33:15 -06:00
|
|
|
if req.Method == "HEAD" {
|
|
|
|
return
|
|
|
|
}
|
2011-04-27 15:23:25 -06:00
|
|
|
|
|
|
|
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()))
|
|
|
|
}()
|
|
|
|
}
|
2012-02-10 00:49:19 -07:00
|
|
|
gz := gzip.NewWriter(w)
|
2011-04-12 10:35:07 -06:00
|
|
|
gz.Write([]byte(testString))
|
2011-04-27 15:23:25 -06:00
|
|
|
if req.FormValue("body") == "large" {
|
2011-09-30 14:13:39 -06:00
|
|
|
io.CopyN(gz, rand.Reader, nRandBytes)
|
2011-04-27 15:23:25 -06:00
|
|
|
}
|
|
|
|
gz.Close()
|
2011-04-12 10:35:07 -06:00
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
|
2011-04-27 15:23:25 -06:00
|
|
|
for _, chunked := range []string{"1", "0"} {
|
|
|
|
c := &Client{Transport: &Transport{}}
|
|
|
|
|
|
|
|
// First fetch something large, but only read some of it.
|
2011-10-12 12:48:25 -06:00
|
|
|
res, err := c.Get(ts.URL + "/?body=large&chunked=" + chunked)
|
2011-04-27 15:23:25 -06:00
|
|
|
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.
|
2011-10-12 12:48:25 -06:00
|
|
|
res, err = c.Get(ts.URL + "/?chunked=" + chunked)
|
2011-04-27 15:23:25 -06:00
|
|
|
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)
|
|
|
|
}
|
2011-04-12 10:35:07 -06:00
|
|
|
}
|
2011-05-11 20:33:15 -06:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
2011-04-12 10:35:07 -06:00
|
|
|
}
|
|
|
|
|
2011-05-18 10:23:29 -06:00
|
|
|
func TestTransportProxy(t *testing.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()
|
|
|
|
|
2011-08-16 21:36:02 -06:00
|
|
|
pu, err := url.Parse(proxy.URL)
|
2011-05-18 10:23:29 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-12 10:35:07 -06:00
|
|
|
// 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) {
|
|
|
|
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{}}
|
2011-05-13 08:31:24 -06:00
|
|
|
res, err := c.Get(ts.URL)
|
2011-04-12 10:35:07 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-13 18:48:56 -07:00
|
|
|
// tests that persistent goroutine connections shut down when no longer desired.
|
|
|
|
func TestTransportPersistConnLeak(t *testing.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}
|
|
|
|
|
2012-02-16 14:49:41 -07:00
|
|
|
n0 := runtime.NumGoroutine()
|
2012-02-13 18:48:56 -07:00
|
|
|
|
2012-02-13 21:26:09 -07:00
|
|
|
const numReq = 25
|
2012-02-13 18:48:56 -07:00
|
|
|
didReqCh := make(chan bool)
|
|
|
|
for i := 0; i < numReq; i++ {
|
|
|
|
go func() {
|
2012-02-13 21:26:09 -07:00
|
|
|
res, err := c.Get(ts.URL)
|
2012-02-13 18:48:56 -07:00
|
|
|
didReqCh <- true
|
2012-02-13 21:26:09 -07:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("client fetch error: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
res.Body.Close()
|
2012-02-13 18:48:56 -07:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for all goroutines to be stuck in the Handler.
|
|
|
|
for i := 0; i < numReq; i++ {
|
|
|
|
<-gotReqCh
|
|
|
|
}
|
|
|
|
|
2012-02-16 14:49:41 -07:00
|
|
|
nhigh := runtime.NumGoroutine()
|
2012-02-13 18:48:56 -07:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2012-02-13 21:26:09 -07:00
|
|
|
tr.CloseIdleConnections()
|
2012-02-13 18:48:56 -07:00
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
runtime.GC()
|
|
|
|
runtime.GC() // even more.
|
2012-02-16 14:49:41 -07:00
|
|
|
nfinal := runtime.NumGoroutine()
|
2012-02-13 18:48:56 -07:00
|
|
|
|
|
|
|
growth := nfinal - n0
|
|
|
|
|
2012-02-13 21:26:09 -07:00
|
|
|
// 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")
|
2012-02-13 18:48:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-09 17:27:32 -07:00
|
|
|
// This used to crash; http://golang.org/issue/3266
|
|
|
|
func TestTransportIdleConnCrash(t *testing.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
|
|
|
|
}
|
|
|
|
|
2012-05-21 11:39:31 -06:00
|
|
|
// 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) {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-19 10:20:41 -06:00
|
|
|
// 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) {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-29 19:00:51 -07:00
|
|
|
// From http://golang.org/issue/4454 ,
|
|
|
|
// "client fails to handle requests with no body and chunked encoding"
|
|
|
|
func TestChunkedNoContent(t *testing.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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-11 17:40:44 -06:00
|
|
|
func TestTransportConcurrency(t *testing.T) {
|
|
|
|
const maxProcs = 16
|
|
|
|
const numReqs = 500
|
|
|
|
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()
|
|
|
|
tr := &Transport{}
|
|
|
|
c := &Client{Transport: tr}
|
|
|
|
reqs := make(chan string)
|
|
|
|
defer close(reqs)
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(numReqs)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
wg.Done()
|
|
|
|
res.Body.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
for i := 0; i < numReqs; i++ {
|
|
|
|
reqs <- fmt.Sprintf("request-%d", i)
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
net/http: fix Transport races & deadlocks
Thanks to Dustin Sallings for exposing the most frustrating
bug ever, and for providing repro cases (which formed the
basis of the new tests in this CL), and to Dave Cheney and
Dmitry Vyukov for help debugging and fixing.
This CL depends on submited pollster CLs ffd1e075c260 (Unix)
and 14b544194509 (Windows), as well as unsubmitted 6852085.
Some operating systems (OpenBSD, NetBSD, ?) may still require
more pollster work, fixing races (Issue 4434 and
http://goo.gl/JXB6W).
Tested on linux-amd64 and darwin-amd64, both with GOMAXPROCS 1
and 4 (all combinations of which previously failed differently)
Fixes #4191
Update #4434 (related fallout from this bug)
R=dave, bradfitz, dsallings, rsc, fullung
CC=golang-dev
https://golang.org/cl/6851061
2012-11-26 14:31:02 -07:00
|
|
|
func TestIssue4191_InfiniteGetTimeout(t *testing.T) {
|
|
|
|
const debug = false
|
|
|
|
mux := NewServeMux()
|
|
|
|
mux.HandleFunc("/get", func(w ResponseWriter, r *Request) {
|
|
|
|
io.Copy(w, neverEnding('a'))
|
|
|
|
})
|
|
|
|
ts := httptest.NewServer(mux)
|
|
|
|
|
|
|
|
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(100 * time.Millisecond))
|
|
|
|
if debug {
|
|
|
|
conn = NewLoggingConn("client", conn)
|
|
|
|
}
|
|
|
|
return conn, nil
|
|
|
|
},
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
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) {
|
|
|
|
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)
|
|
|
|
|
|
|
|
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(100 * time.Millisecond))
|
|
|
|
if debug {
|
|
|
|
conn = NewLoggingConn("client", conn)
|
|
|
|
}
|
|
|
|
return conn, nil
|
|
|
|
},
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
t.Errorf("Error issuing GET: %v", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
req, _ := NewRequest("PUT", ts.URL+"/put", sres.Body)
|
|
|
|
_, err = client.Do(req)
|
|
|
|
if err == nil {
|
2012-11-27 23:00:50 -07:00
|
|
|
sres.Body.Close()
|
net/http: fix Transport races & deadlocks
Thanks to Dustin Sallings for exposing the most frustrating
bug ever, and for providing repro cases (which formed the
basis of the new tests in this CL), and to Dave Cheney and
Dmitry Vyukov for help debugging and fixing.
This CL depends on submited pollster CLs ffd1e075c260 (Unix)
and 14b544194509 (Windows), as well as unsubmitted 6852085.
Some operating systems (OpenBSD, NetBSD, ?) may still require
more pollster work, fixing races (Issue 4434 and
http://goo.gl/JXB6W).
Tested on linux-amd64 and darwin-amd64, both with GOMAXPROCS 1
and 4 (all combinations of which previously failed differently)
Fixes #4191
Update #4434 (related fallout from this bug)
R=dave, bradfitz, dsallings, rsc, fullung
CC=golang-dev
https://golang.org/cl/6851061
2012-11-26 14:31:02 -07:00
|
|
|
t.Errorf("Unexpected successful PUT")
|
|
|
|
break
|
|
|
|
}
|
2012-11-27 23:00:50 -07:00
|
|
|
sres.Body.Close()
|
net/http: fix Transport races & deadlocks
Thanks to Dustin Sallings for exposing the most frustrating
bug ever, and for providing repro cases (which formed the
basis of the new tests in this CL), and to Dave Cheney and
Dmitry Vyukov for help debugging and fixing.
This CL depends on submited pollster CLs ffd1e075c260 (Unix)
and 14b544194509 (Windows), as well as unsubmitted 6852085.
Some operating systems (OpenBSD, NetBSD, ?) may still require
more pollster work, fixing races (Issue 4434 and
http://goo.gl/JXB6W).
Tested on linux-amd64 and darwin-amd64, both with GOMAXPROCS 1
and 4 (all combinations of which previously failed differently)
Fixes #4191
Update #4434 (related fallout from this bug)
R=dave, bradfitz, dsallings, rsc, fullung
CC=golang-dev
https://golang.org/cl/6851061
2012-11-26 14:31:02 -07:00
|
|
|
}
|
|
|
|
if debug {
|
|
|
|
println("tests complete; waiting for handlers to finish")
|
|
|
|
}
|
|
|
|
ts.Close()
|
|
|
|
}
|
|
|
|
|
2011-05-25 13:31:11 -06:00
|
|
|
type fooProto struct{}
|
|
|
|
|
2011-11-01 20:04:37 -06:00
|
|
|
func (fooProto) RoundTrip(req *Request) (*Response, error) {
|
2011-05-25 13:31:11 -06:00
|
|
|
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) {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-12 13:23:01 -07:00
|
|
|
func TestTransportNoHost(t *testing.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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-29 10:52:52 -07:00
|
|
|
var proxyFromEnvTests = []struct {
|
|
|
|
env string
|
|
|
|
wanturl string
|
|
|
|
wanterr error
|
|
|
|
}{
|
|
|
|
{"127.0.0.1:8080", "http://127.0.0.1:8080", nil},
|
2012-12-05 20:08:42 -07:00
|
|
|
{"cache.corp.example.com:1234", "http://cache.corp.example.com:1234", nil},
|
|
|
|
{"cache.corp.example.com", "http://cache.corp.example.com", nil},
|
|
|
|
{"https://cache.corp.example.com", "https://cache.corp.example.com", nil},
|
2012-02-29 10:52:52 -07:00
|
|
|
{"http://127.0.0.1:8080", "http://127.0.0.1:8080", nil},
|
|
|
|
{"https://127.0.0.1:8080", "https://127.0.0.1:8080", nil},
|
|
|
|
{"", "<nil>", nil},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProxyFromEnvironment(t *testing.T) {
|
|
|
|
os.Setenv("HTTP_PROXY", "")
|
|
|
|
os.Setenv("http_proxy", "")
|
|
|
|
os.Setenv("NO_PROXY", "")
|
|
|
|
os.Setenv("no_proxy", "")
|
|
|
|
for i, tt := range proxyFromEnvTests {
|
|
|
|
os.Setenv("HTTP_PROXY", tt.env)
|
|
|
|
req, _ := NewRequest("GET", "http://example.com", nil)
|
|
|
|
url, err := ProxyFromEnvironment(req)
|
|
|
|
if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e {
|
|
|
|
t.Errorf("%d. got error = %q, want %q", i, g, e)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if got := fmt.Sprintf("%s", url); got != tt.wanturl {
|
|
|
|
t.Errorf("%d. got URL = %q, want %q", i, url, tt.wanturl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-12 10:35:07 -06:00
|
|
|
// 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,
|
|
|
|
}
|