aboutsummaryrefslogtreecommitdiff
path: root/src/net/http
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2014-10-03 12:22:19 -0400
committerRuss Cox <rsc@golang.org>2014-10-03 12:22:19 -0400
commit904ec0098137f742e0dd96da3bc033d6a0b615d1 (patch)
tree6a05a91443927524fcb78ac82df0142d8e6ae71f /src/net/http
parentd42328c9f749140adf947833e0381fc639c32737 (diff)
parentc65a47f890e33eeed6ee9d8b6d965a5534fb6e0e (diff)
downloadgo-904ec0098137f742e0dd96da3bc033d6a0b615d1.tar.xz
[dev.garbage] merge default into dev.garbage
Diffstat (limited to 'src/net/http')
-rw-r--r--src/net/http/cookie.go10
-rw-r--r--src/net/http/cookie_test.go32
-rw-r--r--src/net/http/export_test.go25
-rw-r--r--src/net/http/httputil/dump.go12
-rw-r--r--src/net/http/httputil/dump_test.go30
-rw-r--r--src/net/http/main_test.go (renamed from src/net/http/z_last_test.go)30
-rw-r--r--src/net/http/request.go3
-rw-r--r--src/net/http/serve_test.go52
-rw-r--r--src/net/http/server.go37
-rw-r--r--src/net/http/transport.go46
-rw-r--r--src/net/http/transport_test.go90
11 files changed, 325 insertions, 42 deletions
diff --git a/src/net/http/cookie.go b/src/net/http/cookie.go
index dc60ba87f5..a0d0fdbbd0 100644
--- a/src/net/http/cookie.go
+++ b/src/net/http/cookie.go
@@ -56,7 +56,7 @@ func readSetCookies(h Header) []*Cookie {
if !isCookieNameValid(name) {
continue
}
- value, success := parseCookieValue(value)
+ value, success := parseCookieValue(value, true)
if !success {
continue
}
@@ -76,7 +76,7 @@ func readSetCookies(h Header) []*Cookie {
attr, val = attr[:j], attr[j+1:]
}
lowerAttr := strings.ToLower(attr)
- val, success = parseCookieValue(val)
+ val, success = parseCookieValue(val, false)
if !success {
c.Unparsed = append(c.Unparsed, parts[i])
continue
@@ -205,7 +205,7 @@ func readCookies(h Header, filter string) []*Cookie {
if filter != "" && filter != name {
continue
}
- val, success := parseCookieValue(val)
+ val, success := parseCookieValue(val, true)
if !success {
continue
}
@@ -345,9 +345,9 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string {
return string(buf)
}
-func parseCookieValue(raw string) (string, bool) {
+func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) {
// Strip the quotes, if present.
- if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
+ if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
raw = raw[1 : len(raw)-1]
}
for i := 0; i < len(raw); i++ {
diff --git a/src/net/http/cookie_test.go b/src/net/http/cookie_test.go
index f78f37299f..98dc2fade0 100644
--- a/src/net/http/cookie_test.go
+++ b/src/net/http/cookie_test.go
@@ -313,6 +313,14 @@ var readCookiesTests = []struct {
{Name: "c2", Value: "v2"},
},
},
+ {
+ Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
+ "",
+ []*Cookie{
+ {Name: "Cookie-1", Value: "v$1"},
+ {Name: "c2", Value: "v2"},
+ },
+ },
}
func TestReadCookies(t *testing.T) {
@@ -327,6 +335,30 @@ func TestReadCookies(t *testing.T) {
}
}
+func TestSetCookieDoubleQuotes(t *testing.T) {
+ res := &Response{Header: Header{}}
+ res.Header.Add("Set-Cookie", `quoted0=none; max-age=30`)
+ res.Header.Add("Set-Cookie", `quoted1="cookieValue"; max-age=31`)
+ res.Header.Add("Set-Cookie", `quoted2=cookieAV; max-age="32"`)
+ res.Header.Add("Set-Cookie", `quoted3="both"; max-age="33"`)
+ got := res.Cookies()
+ want := []*Cookie{
+ {Name: "quoted0", Value: "none", MaxAge: 30},
+ {Name: "quoted1", Value: "cookieValue", MaxAge: 31},
+ {Name: "quoted2", Value: "cookieAV"},
+ {Name: "quoted3", Value: "both"},
+ }
+ if len(got) != len(want) {
+ t.Fatal("got %d cookies, want %d", len(got), len(want))
+ }
+ for i, w := range want {
+ g := got[i]
+ if g.Name != w.Name || g.Value != w.Value || g.MaxAge != w.MaxAge {
+ t.Errorf("cookie #%d:\ngot %v\nwant %v", i, g, w)
+ }
+ }
+}
+
func TestCookieSanitizeValue(t *testing.T) {
defer log.SetOutput(os.Stderr)
var logbuf bytes.Buffer
diff --git a/src/net/http/export_test.go b/src/net/http/export_test.go
index 2c87353554..a6980b5389 100644
--- a/src/net/http/export_test.go
+++ b/src/net/http/export_test.go
@@ -57,6 +57,26 @@ func (t *Transport) IdleConnChMapSizeForTesting() int {
return len(t.idleConnCh)
}
+func (t *Transport) IsIdleForTesting() bool {
+ t.idleMu.Lock()
+ defer t.idleMu.Unlock()
+ return t.wantIdle
+}
+
+func (t *Transport) RequestIdleConnChForTesting() {
+ t.getIdleConnCh(connectMethod{nil, "http", "example.com"})
+}
+
+func (t *Transport) PutIdleTestConn() bool {
+ c, _ := net.Pipe()
+ return t.putIdleConn(&persistConn{
+ t: t,
+ conn: c, // dummy
+ closech: make(chan struct{}), // so it can be closed
+ cacheKey: connectMethodKey{"", "http", "example.com"},
+ })
+}
+
func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
f := func() <-chan time.Time {
return ch
@@ -66,6 +86,7 @@ func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
func ResetCachedEnvironment() {
httpProxyEnv.reset()
+ httpsProxyEnv.reset()
noProxyEnv.reset()
}
@@ -76,3 +97,7 @@ var DefaultUserAgent = defaultUserAgent
func SetPendingDialHooks(before, after func()) {
prePendingDial, postPendingDial = before, after
}
+
+var ExportServerNewConn = (*Server).newConn
+
+var ExportCloseWriteAndWait = (*conn).closeWriteAndWait
diff --git a/src/net/http/httputil/dump.go b/src/net/http/httputil/dump.go
index 2a7a413d01..ac8f103f9b 100644
--- a/src/net/http/httputil/dump.go
+++ b/src/net/http/httputil/dump.go
@@ -95,19 +95,27 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
// with a dummy response.
var buf bytes.Buffer // records the output
pr, pw := io.Pipe()
+ defer pr.Close()
+ defer pw.Close()
dr := &delegateReader{c: make(chan io.Reader)}
// Wait for the request before replying with a dummy response:
go func() {
- http.ReadRequest(bufio.NewReader(pr))
+ req, err := http.ReadRequest(bufio.NewReader(pr))
+ if err == nil {
+ // Ensure all the body is read; otherwise
+ // we'll get a partial dump.
+ io.Copy(ioutil.Discard, req.Body)
+ req.Body.Close()
+ }
dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
}()
t := &http.Transport{
+ DisableKeepAlives: true,
Dial: func(net, addr string) (net.Conn, error) {
return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
},
}
- defer t.CloseIdleConnections()
_, err := t.RoundTrip(reqSend)
diff --git a/src/net/http/httputil/dump_test.go b/src/net/http/httputil/dump_test.go
index e1ffb3935a..024ee5a86f 100644
--- a/src/net/http/httputil/dump_test.go
+++ b/src/net/http/httputil/dump_test.go
@@ -111,6 +111,30 @@ var dumpTests = []dumpTest{
NoBody: true,
},
+
+ // Request with Body > 8196 (default buffer size)
+ {
+ Req: http.Request{
+ Method: "POST",
+ URL: &url.URL{
+ Scheme: "http",
+ Host: "post.tld",
+ Path: "/",
+ },
+ ContentLength: 8193,
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ },
+
+ Body: bytes.Repeat([]byte("a"), 8193),
+
+ WantDumpOut: "POST / HTTP/1.1\r\n" +
+ "Host: post.tld\r\n" +
+ "User-Agent: Go 1.1 package http\r\n" +
+ "Content-Length: 8193\r\n" +
+ "Accept-Encoding: gzip\r\n\r\n" +
+ strings.Repeat("a", 8193),
+ },
}
func TestDumpRequest(t *testing.T) {
@@ -125,6 +149,8 @@ func TestDumpRequest(t *testing.T) {
tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b))
case func() io.ReadCloser:
tt.Req.Body = b()
+ default:
+ t.Fatalf("Test %d: unsupported Body of %T", i, tt.Body)
}
}
setBody()
@@ -159,7 +185,9 @@ func TestDumpRequest(t *testing.T) {
}
}
if dg := runtime.NumGoroutine() - numg0; dg > 4 {
- t.Errorf("Unexpectedly large number of new goroutines: %d new", dg)
+ buf := make([]byte, 4096)
+ buf = buf[:runtime.Stack(buf, true)]
+ t.Errorf("Unexpectedly large number of new goroutines: %d new: %s", dg, buf)
}
}
diff --git a/src/net/http/z_last_test.go b/src/net/http/main_test.go
index 5a0cc11984..9f1dfc3727 100644
--- a/src/net/http/z_last_test.go
+++ b/src/net/http/main_test.go
@@ -5,7 +5,9 @@
package http_test
import (
+ "fmt"
"net/http"
+ "os"
"runtime"
"sort"
"strings"
@@ -13,6 +15,14 @@ import (
"time"
)
+func TestMain(m *testing.M) {
+ v := m.Run()
+ if v == 0 && goroutineLeaked() {
+ os.Exit(1)
+ }
+ os.Exit(v)
+}
+
func interestingGoroutines() (gs []string) {
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
@@ -30,6 +40,7 @@ func interestingGoroutines() (gs []string) {
// These only show up with GOTRACEBACK=2; Issue 5005 (comment 28)
strings.Contains(stack, "runtime.goexit") ||
strings.Contains(stack, "created by runtime.gc") ||
+ strings.Contains(stack, "net/http_test.interestingGoroutines") ||
strings.Contains(stack, "runtime.MHeap_Scavenger") {
continue
}
@@ -40,10 +51,10 @@ func interestingGoroutines() (gs []string) {
}
// Verify the other tests didn't leave any goroutines running.
-// This is in a file named z_last_test.go so it sorts at the end.
-func TestGoroutinesRunning(t *testing.T) {
+func goroutineLeaked() bool {
if testing.Short() {
- t.Skip("not counting goroutines for leakage in -short mode")
+ // not counting goroutines for leakage in -short mode
+ return false
}
gs := interestingGoroutines()
@@ -54,13 +65,14 @@ func TestGoroutinesRunning(t *testing.T) {
n++
}
- t.Logf("num goroutines = %d", n)
- if n > 0 {
- t.Error("Too many goroutines.")
- for stack, count := range stackCount {
- t.Logf("%d instances of:\n%s", count, stack)
- }
+ if n == 0 {
+ return false
+ }
+ fmt.Fprintf(os.Stderr, "Too many goroutines running after net/http test(s).\n")
+ for stack, count := range stackCount {
+ fmt.Fprintf(os.Stderr, "%d instances of:\n%s", count, stack)
}
+ return true
}
func afterTest(t *testing.T) {
diff --git a/src/net/http/request.go b/src/net/http/request.go
index 263c26c9bd..487eebcb84 100644
--- a/src/net/http/request.go
+++ b/src/net/http/request.go
@@ -852,7 +852,8 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error {
// POST and PUT body parameters take precedence over URL query string values.
// FormValue calls ParseMultipartForm and ParseForm if necessary and ignores
// any errors returned by these functions.
-// To access multiple values of the same key use ParseForm.
+// To access multiple values of the same key, call ParseForm and
+// then inspect Request.Form directly.
func (r *Request) FormValue(key string) string {
if r.Form == nil {
r.ParseMultipartForm(defaultMaxMemory)
diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go
index ee4f204995..702bffdc13 100644
--- a/src/net/http/serve_test.go
+++ b/src/net/http/serve_test.go
@@ -778,6 +778,35 @@ func TestChunkedResponseHeaders(t *testing.T) {
}
}
+func TestIdentityResponseHeaders(t *testing.T) {
+ defer afterTest(t)
+ log.SetOutput(ioutil.Discard) // is noisy otherwise
+ defer log.SetOutput(os.Stderr)
+
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ w.Header().Set("Transfer-Encoding", "identity")
+ w.(Flusher).Flush()
+ fmt.Fprintf(w, "I am an identity response.")
+ }))
+ defer ts.Close()
+
+ res, err := Get(ts.URL)
+ if err != nil {
+ t.Fatalf("Get error: %v", err)
+ }
+ defer res.Body.Close()
+
+ if g, e := res.TransferEncoding, []string(nil); !reflect.DeepEqual(g, e) {
+ t.Errorf("expected TransferEncoding of %v; got %v", e, g)
+ }
+ if _, haveCL := res.Header["Content-Length"]; haveCL {
+ t.Errorf("Unexpected Content-Length")
+ }
+ if !res.Close {
+ t.Errorf("expected Connection: close; got %v", res.Close)
+ }
+}
+
// Test304Responses verifies that 304s don't declare that they're
// chunking in their response headers and aren't allowed to produce
// output.
@@ -2607,6 +2636,29 @@ func TestServerConnStateNew(t *testing.T) {
}
}
+type closeWriteTestConn struct {
+ rwTestConn
+ didCloseWrite bool
+}
+
+func (c *closeWriteTestConn) CloseWrite() error {
+ c.didCloseWrite = true
+ return nil
+}
+
+func TestCloseWrite(t *testing.T) {
+ var srv Server
+ var testConn closeWriteTestConn
+ c, err := ExportServerNewConn(&srv, &testConn)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ExportCloseWriteAndWait(c)
+ if !testConn.didCloseWrite {
+ t.Error("didn't see CloseWrite call")
+ }
+}
+
func BenchmarkClientServer(b *testing.B) {
b.ReportAllocs()
b.StopTimer()
diff --git a/src/net/http/server.go b/src/net/http/server.go
index 203037e9f5..b5959f7321 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -42,6 +42,12 @@ var (
// and then return. Returning signals that the request is finished
// and that the HTTP server can move on to the next request on
// the connection.
+//
+// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
+// that the effect of the panic was isolated to the active request.
+// It recovers the panic, logs a stack trace to the server error log,
+// and hangs up the connection.
+//
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
@@ -833,13 +839,20 @@ func (cw *chunkWriter) writeHeader(p []byte) {
} else if hasCL {
delHeader("Transfer-Encoding")
} else if w.req.ProtoAtLeast(1, 1) {
- // HTTP/1.1 or greater: use chunked transfer encoding
- // to avoid closing the connection at EOF.
- // TODO: this blows away any custom or stacked Transfer-Encoding they
- // might have set. Deal with that as need arises once we have a valid
- // use case.
- cw.chunking = true
- setHeader.transferEncoding = "chunked"
+ // HTTP/1.1 or greater: Transfer-Encoding has been set to identity, and no
+ // content-length has been provided. The connection must be closed after the
+ // reply is written, and no chunking is to be done. This is the setup
+ // recommended in the Server-Sent Events candidate recommendation 11,
+ // section 8.
+ if hasTE && te == "identity" {
+ cw.chunking = false
+ w.closeAfterReply = true
+ } else {
+ // HTTP/1.1 or greater: use chunked transfer encoding
+ // to avoid closing the connection at EOF.
+ cw.chunking = true
+ setHeader.transferEncoding = "chunked"
+ }
} else {
// HTTP version < 1.1: cannot do chunked transfer
// encoding and we don't know the Content-Length so
@@ -1058,15 +1071,21 @@ func (c *conn) close() {
// This timeout is somewhat arbitrary (~latency around the planet).
const rstAvoidanceDelay = 500 * time.Millisecond
+type closeWriter interface {
+ CloseWrite() error
+}
+
+var _ closeWriter = (*net.TCPConn)(nil)
+
// closeWrite flushes any outstanding data and sends a FIN packet (if
// client is connected via TCP), signalling that we're done. We then
-// pause for a bit, hoping the client processes it before `any
+// pause for a bit, hoping the client processes it before any
// subsequent RST.
//
// See http://golang.org/issue/3595
func (c *conn) closeWriteAndWait() {
c.finalFlush()
- if tcp, ok := c.rwc.(*net.TCPConn); ok {
+ if tcp, ok := c.rwc.(closeWriter); ok {
tcp.CloseWrite()
}
time.Sleep(rstAvoidanceDelay)
diff --git a/src/net/http/transport.go b/src/net/http/transport.go
index 527ed8bdd1..70e574fc86 100644
--- a/src/net/http/transport.go
+++ b/src/net/http/transport.go
@@ -47,13 +47,16 @@ const DefaultMaxIdleConnsPerHost = 2
// HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT).
// Transport can also cache connections for future re-use.
type Transport struct {
- idleMu sync.Mutex
- idleConn map[connectMethodKey][]*persistConn
- idleConnCh map[connectMethodKey]chan *persistConn
+ idleMu sync.Mutex
+ wantIdle bool // user has requested to close all idle conns
+ idleConn map[connectMethodKey][]*persistConn
+ idleConnCh map[connectMethodKey]chan *persistConn
+
reqMu sync.Mutex
reqCanceler map[*Request]func()
- altMu sync.RWMutex
- altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper
+
+ altMu sync.RWMutex
+ altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
@@ -116,15 +119,28 @@ type Transport struct {
// ProxyFromEnvironment returns the URL of the proxy to use for a
// given request, as indicated by the environment variables
-// $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy).
-// An error is returned if the proxy environment is invalid.
+// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
+// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
+// requests.
+//
+// The environment values may be either a complete URL or a
+// "host[:port]", in which case the "http" scheme is assumed.
+// An error is returned if the value is a different form.
+//
// A nil URL and nil error are returned if no proxy is defined in the
-// environment, or a proxy should not be used for the given request.
+// environment, or a proxy should not be used for the given request,
+// as defined by NO_PROXY.
//
// As a special case, if req.URL.Host is "localhost" (with or without
// a port number), then a nil URL and nil error will be returned.
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
- proxy := httpProxyEnv.Get()
+ var proxy string
+ if req.URL.Scheme == "https" {
+ proxy = httpsProxyEnv.Get()
+ }
+ if proxy == "" {
+ proxy = httpProxyEnv.Get()
+ }
if proxy == "" {
return nil, nil
}
@@ -249,6 +265,7 @@ func (t *Transport) CloseIdleConnections() {
m := t.idleConn
t.idleConn = nil
t.idleConnCh = nil
+ t.wantIdle = true
t.idleMu.Unlock()
for _, conns := range m {
for _, pconn := range conns {
@@ -276,6 +293,9 @@ var (
httpProxyEnv = &envOnce{
names: []string{"HTTP_PROXY", "http_proxy"},
}
+ httpsProxyEnv = &envOnce{
+ names: []string{"HTTPS_PROXY", "https_proxy"},
+ }
noProxyEnv = &envOnce{
names: []string{"NO_PROXY", "no_proxy"},
}
@@ -316,7 +336,7 @@ func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectM
if t.Proxy != nil {
cm.proxyURL, err = t.Proxy(treq.Request)
}
- return cm, nil
+ return cm, err
}
// proxyAuth returns the Proxy-Authorization header to set
@@ -369,6 +389,11 @@ func (t *Transport) putIdleConn(pconn *persistConn) bool {
delete(t.idleConnCh, key)
}
}
+ if t.wantIdle {
+ t.idleMu.Unlock()
+ pconn.close()
+ return false
+ }
if t.idleConn == nil {
t.idleConn = make(map[connectMethodKey][]*persistConn)
}
@@ -397,6 +422,7 @@ func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn {
key := cm.key()
t.idleMu.Lock()
defer t.idleMu.Unlock()
+ t.wantIdle = false
if t.idleConnCh == nil {
t.idleConnCh = make(map[connectMethodKey]chan *persistConn)
}
diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go
index 3460d690e3..66fcc3c7d4 100644
--- a/src/net/http/transport_test.go
+++ b/src/net/http/transport_test.go
@@ -1701,26 +1701,40 @@ Content-Length: %d
}
type proxyFromEnvTest struct {
- req string // URL to fetch; blank means "http://example.com"
- env string
- noenv string
+ 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 != "" {
- fmt.Fprintf(&buf, " no_proxy=%q", t.noenv)
+ space()
+ fmt.Fprintf(&buf, "no_proxy=%q", t.noenv)
}
req := "http://example.com"
if t.req != "" {
req = t.req
}
- fmt.Fprintf(&buf, " req=%q", req)
+ space()
+ fmt.Fprintf(&buf, "req=%q", req)
return strings.TrimSpace(buf.String())
}
@@ -1731,7 +1745,15 @@ var proxyFromEnvTests = []proxyFromEnvTest{
{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"},
@@ -1743,6 +1765,7 @@ 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
@@ -2136,6 +2159,63 @@ func TestTransportDialTLS(t *testing.T) {
}
}
+// Test for issue 8755
+// Ensure that if a proxy returns an error, it is exposed by RoundTrip
+func TestRoundTripReturnsProxyError(t *testing.T) {
+ badProxy := func(*http.Request) (*url.URL, error) {
+ return nil, errors.New("errorMessage")
+ }
+
+ tr := &Transport{Proxy: badProxy}
+
+ req, _ := http.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)
+}
+
func wantBody(res *http.Response, err error, want string) error {
if err != nil {
return err