diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2014-07-19 08:53:52 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2014-07-19 08:53:52 +0000 |
commit | 00d86ac99f5dd6afa5bbd7c38ffe1c585edd2387 (patch) | |
tree | b988e32ea14a3dc1b4718b1fdfa47bab087ae96c /libgo/go/net | |
parent | bcf2fc6ee0a7edbe7de4299f28b66527c07bb0a2 (diff) | |
download | gcc-00d86ac99f5dd6afa5bbd7c38ffe1c585edd2387.tar.gz |
libgo: Update to Go 1.3 release.
From-SVN: r212837
Diffstat (limited to 'libgo/go/net')
103 files changed, 3649 insertions, 825 deletions
diff --git a/libgo/go/net/cgo_bsd.go b/libgo/go/net/cgo_bsd.go index 3852fc22987..ce46f2e8c3a 100644 --- a/libgo/go/net/cgo_bsd.go +++ b/libgo/go/net/cgo_bsd.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build !netgo -// +build darwin dragonfly freebsd +// +build darwin dragonfly freebsd solaris package net diff --git a/libgo/go/net/conn_test.go b/libgo/go/net/conn_test.go index 7250dcb85ad..37bb4e2c071 100644 --- a/libgo/go/net/conn_test.go +++ b/libgo/go/net/conn_test.go @@ -16,11 +16,11 @@ import ( var connTests = []struct { net string - addr func() string + addr string }{ - {"tcp", func() string { return "127.0.0.1:0" }}, - {"unix", testUnixAddr}, - {"unixpacket", testUnixAddr}, + {"tcp", "127.0.0.1:0"}, + {"unix", testUnixAddr()}, + {"unixpacket", testUnixAddr()}, } // someTimeout is used just to test that net.Conn implementations @@ -31,18 +31,21 @@ const someTimeout = 10 * time.Second func TestConnAndListener(t *testing.T) { for _, tt := range connTests { switch tt.net { - case "unix", "unixpacket": + case "unix": switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": continue } - if tt.net == "unixpacket" && runtime.GOOS != "linux" { + case "unixpacket": + switch runtime.GOOS { + case "darwin", "nacl", "openbsd", "plan9", "windows": + continue + case "freebsd": // FreeBSD 8 doesn't support unixpacket continue } } - addr := tt.addr() - ln, err := Listen(tt.net, addr) + ln, err := Listen(tt.net, tt.addr) if err != nil { t.Fatalf("Listen failed: %v", err) } @@ -52,7 +55,7 @@ func TestConnAndListener(t *testing.T) { case "unix", "unixpacket": os.Remove(addr) } - }(ln, tt.net, addr) + }(ln, tt.net, tt.addr) if ln.Addr().Network() != tt.net { t.Fatalf("got %v; expected %v", ln.Addr().Network(), tt.net) } diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go index 70b66e70d15..93569c253cd 100644 --- a/libgo/go/net/dial.go +++ b/libgo/go/net/dial.go @@ -44,6 +44,12 @@ type Dialer struct { // destination is a host name that has multiple address family // DNS records. DualStack bool + + // KeepAlive specifies the keep-alive period for an active + // network connection. + // If zero, keep-alives are not enabled. Network protocols + // that do not support keep-alives ignore this field. + KeepAlive time.Duration } // Return either now+Timeout or Deadline, whichever comes first. @@ -162,9 +168,19 @@ func (d *Dialer) Dial(network, address string) (Conn, error) { return dialMulti(network, address, d.LocalAddr, ras, deadline) } } - return dial(network, ra.toAddr(), dialer, d.deadline()) + c, err := dial(network, ra.toAddr(), dialer, d.deadline()) + if d.KeepAlive > 0 && err == nil { + if tc, ok := c.(*TCPConn); ok { + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(d.KeepAlive) + testHookSetKeepAlive() + } + } + return c, err } +var testHookSetKeepAlive = func() {} // changed by dial_test.go + // dialMulti attempts to establish connections to each destination of // the list of addresses. It will return the first established // connection and close the other connections. Otherwise it returns diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go index 08a0567ca76..f9260fd281b 100644 --- a/libgo/go/net/dial_test.go +++ b/libgo/go/net/dial_test.go @@ -425,60 +425,6 @@ func numFD() int { panic("numFDs not implemented on " + runtime.GOOS) } -// Assert that a failed Dial attempt does not leak -// runtime.PollDesc structures -func TestDialFailPDLeak(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") - } - if runtime.GOOS == "windows" && runtime.GOARCH == "386" { - // Just skip the test because it takes too long. - t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH) - } - - maxprocs := runtime.GOMAXPROCS(0) - loops := 10 + maxprocs - // 500 is enough to turn over the chunk of pollcache. - // See allocPollDesc in runtime/netpoll.goc. - const count = 500 - var old runtime.MemStats // used by sysdelta - runtime.ReadMemStats(&old) - sysdelta := func() uint64 { - var new runtime.MemStats - runtime.ReadMemStats(&new) - delta := old.Sys - new.Sys - old = new - return delta - } - d := &Dialer{Timeout: time.Nanosecond} // don't bother TCP with handshaking - failcount := 0 - for i := 0; i < loops; i++ { - var wg sync.WaitGroup - for i := 0; i < count; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if c, err := d.Dial("tcp", "127.0.0.1:1"); err == nil { - t.Error("dial should not succeed") - c.Close() - } - }() - } - wg.Wait() - if t.Failed() { - t.FailNow() - } - if delta := sysdelta(); delta > 0 { - failcount++ - } - // there are always some allocations on the first loop - if failcount > maxprocs+2 { - t.Error("detected possible memory leak in runtime") - t.FailNow() - } - } -} - func TestDialer(t *testing.T) { ln, err := Listen("tcp4", "127.0.0.1:0") if err != nil { @@ -555,3 +501,36 @@ func TestDialDualStackLocalhost(t *testing.T) { } } } + +func TestDialerKeepAlive(t *testing.T) { + ln := newLocalListener(t) + defer ln.Close() + defer func() { + testHookSetKeepAlive = func() {} + }() + go func() { + for { + c, err := ln.Accept() + if err != nil { + return + } + c.Close() + } + }() + for _, keepAlive := range []bool{false, true} { + got := false + testHookSetKeepAlive = func() { got = true } + var d Dialer + if keepAlive { + d.KeepAlive = 30 * time.Second + } + c, err := d.Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + c.Close() + if got != keepAlive { + t.Errorf("Dialer.KeepAlive = %v: SetKeepAlive called = %v, want %v", d.KeepAlive, got, !got) + } + } +} diff --git a/libgo/go/net/dnsclient.go b/libgo/go/net/dnsclient.go index 01db4372945..9bffa11f916 100644 --- a/libgo/go/net/dnsclient.go +++ b/libgo/go/net/dnsclient.go @@ -191,10 +191,10 @@ func (addrs byPriorityWeight) shuffleByWeight() { } for sum > 0 && len(addrs) > 1 { s := 0 - n := rand.Intn(sum + 1) + n := rand.Intn(sum) for i := range addrs { s += int(addrs[i].Weight) - if s >= n { + if s > n { if i > 0 { t := addrs[i] copy(addrs[1:i+1], addrs[0:i]) diff --git a/libgo/go/net/dnsclient_test.go b/libgo/go/net/dnsclient_test.go new file mode 100644 index 00000000000..435eb35506e --- /dev/null +++ b/libgo/go/net/dnsclient_test.go @@ -0,0 +1,69 @@ +// Copyright 2014 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. + +package net + +import ( + "math/rand" + "testing" +) + +func checkDistribution(t *testing.T, data []*SRV, margin float64) { + sum := 0 + for _, srv := range data { + sum += int(srv.Weight) + } + + results := make(map[string]int) + + count := 1000 + for j := 0; j < count; j++ { + d := make([]*SRV, len(data)) + copy(d, data) + byPriorityWeight(d).shuffleByWeight() + key := d[0].Target + results[key] = results[key] + 1 + } + + actual := results[data[0].Target] + expected := float64(count) * float64(data[0].Weight) / float64(sum) + diff := float64(actual) - expected + t.Logf("actual: %v diff: %v e: %v m: %v", actual, diff, expected, margin) + if diff < 0 { + diff = -diff + } + if diff > (expected * margin) { + t.Errorf("missed target weight: expected %v, %v", expected, actual) + } +} + +func testUniformity(t *testing.T, size int, margin float64) { + rand.Seed(1) + data := make([]*SRV, size) + for i := 0; i < size; i++ { + data[i] = &SRV{Target: string('a' + i), Weight: 1} + } + checkDistribution(t, data, margin) +} + +func TestUniformity(t *testing.T) { + testUniformity(t, 2, 0.05) + testUniformity(t, 3, 0.10) + testUniformity(t, 10, 0.20) + testWeighting(t, 0.05) +} + +func testWeighting(t *testing.T, margin float64) { + rand.Seed(1) + data := []*SRV{ + {Target: "a", Weight: 60}, + {Target: "b", Weight: 30}, + {Target: "c", Weight: 10}, + } + checkDistribution(t, data, margin) +} + +func TestWeighting(t *testing.T) { + testWeighting(t, 0.05) +} diff --git a/libgo/go/net/dnsclient_unix.go b/libgo/go/net/dnsclient_unix.go index a30c9a73d7e..3713efd0e3c 100644 --- a/libgo/go/net/dnsclient_unix.go +++ b/libgo/go/net/dnsclient_unix.go @@ -2,13 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris // DNS client: see RFC 1035. // Has to be linked into package net for Dial. // TODO(rsc): -// Check periodically whether /etc/resolv.conf has changed. // Could potentially handle many outstanding lookups faster. // Could have a small cache. // Random UDP source port (net.Dial should do that for us). @@ -19,6 +18,7 @@ package net import ( "io" "math/rand" + "os" "sync" "time" ) @@ -156,33 +156,90 @@ func convertRR_AAAA(records []dnsRR) []IP { return addrs } -var cfg *dnsConfig -var dnserr error +var cfg struct { + ch chan struct{} + mu sync.RWMutex // protects dnsConfig and dnserr + dnsConfig *dnsConfig + dnserr error +} +var onceLoadConfig sync.Once // Assume dns config file is /etc/resolv.conf here -func loadConfig() { cfg, dnserr = dnsReadConfig("/etc/resolv.conf") } +func loadDefaultConfig() { + loadConfig("/etc/resolv.conf", 5*time.Second, nil) +} -var onceLoadConfig sync.Once +func loadConfig(resolvConfPath string, reloadTime time.Duration, quit <-chan chan struct{}) { + var mtime time.Time + cfg.ch = make(chan struct{}, 1) + if fi, err := os.Stat(resolvConfPath); err != nil { + cfg.dnserr = err + } else { + mtime = fi.ModTime() + cfg.dnsConfig, cfg.dnserr = dnsReadConfig(resolvConfPath) + } + go func() { + for { + time.Sleep(reloadTime) + select { + case qresp := <-quit: + qresp <- struct{}{} + return + case <-cfg.ch: + } + + // In case of error, we keep the previous config + fi, err := os.Stat(resolvConfPath) + if err != nil { + continue + } + // If the resolv.conf mtime didn't change, do not reload + m := fi.ModTime() + if m.Equal(mtime) { + continue + } + mtime = m + // In case of error, we keep the previous config + ncfg, err := dnsReadConfig(resolvConfPath) + if err != nil || len(ncfg.servers) == 0 { + continue + } + cfg.mu.Lock() + cfg.dnsConfig = ncfg + cfg.dnserr = nil + cfg.mu.Unlock() + } + }() +} func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) { if !isDomainName(name) { return name, nil, &DNSError{Err: "invalid domain name", Name: name} } - onceLoadConfig.Do(loadConfig) - if dnserr != nil || cfg == nil { - err = dnserr + onceLoadConfig.Do(loadDefaultConfig) + + select { + case cfg.ch <- struct{}{}: + default: + } + + cfg.mu.RLock() + defer cfg.mu.RUnlock() + + if cfg.dnserr != nil || cfg.dnsConfig == nil { + err = cfg.dnserr return } // If name is rooted (trailing dot) or has enough dots, // try it by itself first. rooted := len(name) > 0 && name[len(name)-1] == '.' - if rooted || count(name, '.') >= cfg.ndots { + if rooted || count(name, '.') >= cfg.dnsConfig.ndots { rname := name if !rooted { rname += "." } // Can try as ordinary name. - cname, addrs, err = tryOneName(cfg, rname, qtype) + cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) if err == nil { return } @@ -192,12 +249,12 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) } // Otherwise, try suffixes. - for i := 0; i < len(cfg.search); i++ { - rname := name + "." + cfg.search[i] + for i := 0; i < len(cfg.dnsConfig.search); i++ { + rname := name + "." + cfg.dnsConfig.search[i] if rname[len(rname)-1] != '.' { rname += "." } - cname, addrs, err = tryOneName(cfg, rname, qtype) + cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) if err == nil { return } @@ -208,7 +265,7 @@ func lookup(name string, qtype uint16) (cname string, addrs []dnsRR, err error) if !rooted { rname += "." } - cname, addrs, err = tryOneName(cfg, rname, qtype) + cname, addrs, err = tryOneName(cfg.dnsConfig, rname, qtype) if err == nil { return } @@ -233,11 +290,6 @@ func goLookupHost(name string) (addrs []string, err error) { if len(addrs) > 0 { return } - onceLoadConfig.Do(loadConfig) - if dnserr != nil || cfg == nil { - err = dnserr - return - } ips, err := goLookupIP(name) if err != nil { return @@ -268,11 +320,6 @@ func goLookupIP(name string) (addrs []IP, err error) { return } } - onceLoadConfig.Do(loadConfig) - if dnserr != nil || cfg == nil { - err = dnserr - return - } var records []dnsRR var cname string var err4, err6 error @@ -308,11 +355,6 @@ func goLookupIP(name string) (addrs []IP, err error) { // depending on our lookup code, so that Go and C get the same // answers. func goLookupCNAME(name string) (cname string, err error) { - onceLoadConfig.Do(loadConfig) - if dnserr != nil || cfg == nil { - err = dnserr - return - } _, rr, err := lookup(name, dnsTypeCNAME) if err != nil { return diff --git a/libgo/go/net/dnsclient_unix_test.go b/libgo/go/net/dnsclient_unix_test.go index 47dcb563bc5..2350142d610 100644 --- a/libgo/go/net/dnsclient_unix_test.go +++ b/libgo/go/net/dnsclient_unix_test.go @@ -2,12 +2,18 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net import ( + "io" + "io/ioutil" + "os" + "path" + "reflect" "testing" + "time" ) func TestTCPLookup(t *testing.T) { @@ -25,3 +31,129 @@ func TestTCPLookup(t *testing.T) { t.Fatalf("exchange failed: %v", err) } } + +type resolvConfTest struct { + *testing.T + dir string + path string + started bool + quitc chan chan struct{} +} + +func newResolvConfTest(t *testing.T) *resolvConfTest { + dir, err := ioutil.TempDir("", "resolvConfTest") + if err != nil { + t.Fatalf("could not create temp dir: %v", err) + } + + // Disable the default loadConfig + onceLoadConfig.Do(func() {}) + + r := &resolvConfTest{ + T: t, + dir: dir, + path: path.Join(dir, "resolv.conf"), + quitc: make(chan chan struct{}), + } + + return r +} + +func (r *resolvConfTest) Start() { + loadConfig(r.path, 100*time.Millisecond, r.quitc) + r.started = true +} + +func (r *resolvConfTest) SetConf(s string) { + // Make sure the file mtime will be different once we're done here, + // even on systems with coarse (1s) mtime resolution. + time.Sleep(time.Second) + + f, err := os.OpenFile(r.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { + r.Fatalf("failed to create temp file %s: %v", r.path, err) + } + if _, err := io.WriteString(f, s); err != nil { + f.Close() + r.Fatalf("failed to write temp file: %v", err) + } + f.Close() + + if r.started { + cfg.ch <- struct{}{} // fill buffer + cfg.ch <- struct{}{} // wait for reload to begin + cfg.ch <- struct{}{} // wait for reload to complete + } +} + +func (r *resolvConfTest) WantServers(want []string) { + cfg.mu.RLock() + defer cfg.mu.RUnlock() + if got := cfg.dnsConfig.servers; !reflect.DeepEqual(got, want) { + r.Fatalf("Unexpected dns server loaded, got %v want %v", got, want) + } +} + +func (r *resolvConfTest) Close() { + resp := make(chan struct{}) + r.quitc <- resp + <-resp + if err := os.RemoveAll(r.dir); err != nil { + r.Logf("failed to remove temp dir %s: %v", r.dir, err) + } +} + +func TestReloadResolvConfFail(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") + } + + r := newResolvConfTest(t) + defer r.Close() + + // resolv.conf.tmp does not exist yet + r.Start() + if _, err := goLookupIP("golang.org"); err == nil { + t.Fatal("goLookupIP(missing) succeeded") + } + + r.SetConf("nameserver 8.8.8.8") + if _, err := goLookupIP("golang.org"); err != nil { + t.Fatalf("goLookupIP(missing; good) failed: %v", err) + } + + // Using a bad resolv.conf while we had a good + // one before should not update the config + r.SetConf("") + if _, err := goLookupIP("golang.org"); err != nil { + t.Fatalf("goLookupIP(missing; good; bad) failed: %v", err) + } +} + +func TestReloadResolvConfChange(t *testing.T) { + if testing.Short() || !*testExternal { + t.Skip("skipping test to avoid external network") + } + + r := newResolvConfTest(t) + defer r.Close() + + r.SetConf("nameserver 8.8.8.8") + r.Start() + + if _, err := goLookupIP("golang.org"); err != nil { + t.Fatalf("goLookupIP(good) failed: %v", err) + } + r.WantServers([]string{"[8.8.8.8]"}) + + // Using a bad resolv.conf when we had a good one + // before should not update the config + r.SetConf("") + if _, err := goLookupIP("golang.org"); err != nil { + t.Fatalf("goLookupIP(good; bad) failed: %v", err) + } + + // A new good config should get picked up + r.SetConf("nameserver 8.8.4.4") + r.WantServers([]string{"[8.8.4.4]"}) +} diff --git a/libgo/go/net/dnsconfig_unix.go b/libgo/go/net/dnsconfig_unix.go index 7856ebc80de..af288253e09 100644 --- a/libgo/go/net/dnsconfig_unix.go +++ b/libgo/go/net/dnsconfig_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris // Read system DNS config from /etc/resolv.conf diff --git a/libgo/go/net/dnsconfig_unix_test.go b/libgo/go/net/dnsconfig_unix_test.go index 697c69f9959..37ed4931dbe 100644 --- a/libgo/go/net/dnsconfig_unix_test.go +++ b/libgo/go/net/dnsconfig_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/fd_mutex_test.go b/libgo/go/net/fd_mutex_test.go index 8383084b7a2..c34ec59b996 100644 --- a/libgo/go/net/fd_mutex_test.go +++ b/libgo/go/net/fd_mutex_test.go @@ -63,7 +63,8 @@ func TestMutexCloseUnblock(t *testing.T) { for i := 0; i < 4; i++ { go func() { if mu.RWLock(true) { - t.Fatal("broken") + t.Error("broken") + return } c <- true }() @@ -138,36 +139,44 @@ func TestMutexStress(t *testing.T) { switch r.Intn(3) { case 0: if !mu.Incref() { - t.Fatal("broken") + t.Error("broken") + return } if mu.Decref() { - t.Fatal("broken") + t.Error("broken") + return } case 1: if !mu.RWLock(true) { - t.Fatal("broken") + t.Error("broken") + return } // Ensure that it provides mutual exclusion for readers. if readState[0] != readState[1] { - t.Fatal("broken") + t.Error("broken") + return } readState[0]++ readState[1]++ if mu.RWUnlock(true) { - t.Fatal("broken") + t.Error("broken") + return } case 2: if !mu.RWLock(false) { - t.Fatal("broken") + t.Error("broken") + return } // Ensure that it provides mutual exclusion for writers. if writeState[0] != writeState[1] { - t.Fatal("broken") + t.Error("broken") + return } writeState[0]++ writeState[1]++ if mu.RWUnlock(false) { - t.Fatal("broken") + t.Error("broken") + return } } } diff --git a/libgo/go/net/fd_plan9.go b/libgo/go/net/fd_plan9.go index 4309a87c3a4..5fe8effc295 100644 --- a/libgo/go/net/fd_plan9.go +++ b/libgo/go/net/fd_plan9.go @@ -150,14 +150,14 @@ func (fd *netFD) Write(b []byte) (n int, err error) { return fd.data.Write(b) } -func (fd *netFD) CloseRead() error { +func (fd *netFD) closeRead() error { if !fd.ok() { return syscall.EINVAL } return syscall.EPLAN9 } -func (fd *netFD) CloseWrite() error { +func (fd *netFD) closeWrite() error { if !fd.ok() { return syscall.EINVAL } diff --git a/libgo/go/net/fd_poll_nacl.go b/libgo/go/net/fd_poll_nacl.go new file mode 100644 index 00000000000..a3701f87648 --- /dev/null +++ b/libgo/go/net/fd_poll_nacl.go @@ -0,0 +1,94 @@ +// Copyright 2013 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. + +package net + +import ( + "syscall" + "time" +) + +type pollDesc struct { + fd *netFD + closing bool +} + +func (pd *pollDesc) Init(fd *netFD) error { pd.fd = fd; return nil } + +func (pd *pollDesc) Close() {} + +func (pd *pollDesc) Lock() {} + +func (pd *pollDesc) Unlock() {} + +func (pd *pollDesc) Wakeup() {} + +func (pd *pollDesc) Evict() bool { + pd.closing = true + if pd.fd != nil { + syscall.StopIO(pd.fd.sysfd) + } + return false +} + +func (pd *pollDesc) Prepare(mode int) error { + if pd.closing { + return errClosing + } + return nil +} + +func (pd *pollDesc) PrepareRead() error { return pd.Prepare('r') } + +func (pd *pollDesc) PrepareWrite() error { return pd.Prepare('w') } + +func (pd *pollDesc) Wait(mode int) error { + if pd.closing { + return errClosing + } + return errTimeout +} + +func (pd *pollDesc) WaitRead() error { return pd.Wait('r') } + +func (pd *pollDesc) WaitWrite() error { return pd.Wait('w') } + +func (pd *pollDesc) WaitCanceled(mode int) {} + +func (pd *pollDesc) WaitCanceledRead() {} + +func (pd *pollDesc) WaitCanceledWrite() {} + +func (fd *netFD) setDeadline(t time.Time) error { + return setDeadlineImpl(fd, t, 'r'+'w') +} + +func (fd *netFD) setReadDeadline(t time.Time) error { + return setDeadlineImpl(fd, t, 'r') +} + +func (fd *netFD) setWriteDeadline(t time.Time) error { + return setDeadlineImpl(fd, t, 'w') +} + +func setDeadlineImpl(fd *netFD, t time.Time, mode int) error { + d := t.UnixNano() + if t.IsZero() { + d = 0 + } + if err := fd.incref(); err != nil { + return err + } + switch mode { + case 'r': + syscall.SetReadDeadline(fd.sysfd, d) + case 'w': + syscall.SetWriteDeadline(fd.sysfd, d) + case 'r' + 'w': + syscall.SetReadDeadline(fd.sysfd, d) + syscall.SetWriteDeadline(fd.sysfd, d) + } + fd.decref() + return nil +} diff --git a/libgo/go/net/fd_poll_runtime.go b/libgo/go/net/fd_poll_runtime.go index e2b2768864a..2bddc836c75 100644 --- a/libgo/go/net/fd_poll_runtime.go +++ b/libgo/go/net/fd_poll_runtime.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux netbsd openbsd windows solaris package net @@ -12,6 +12,9 @@ import ( "time" ) +// runtimeNano returns the current value of the runtime clock in nanoseconds. +func runtimeNano() int64 + func runtime_pollServerInit() func runtime_pollOpen(fd uintptr) (uintptr, int) func runtime_pollClose(ctx uintptr) @@ -128,7 +131,7 @@ func (fd *netFD) setWriteDeadline(t time.Time) error { } func setDeadlineImpl(fd *netFD, t time.Time, mode int) error { - d := t.UnixNano() + d := runtimeNano() + int64(t.Sub(time.Now())) if t.IsZero() { d = 0 } diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go index a89303e37e9..ca6aac3b42e 100644 --- a/libgo/go/net/fd_unix.go +++ b/libgo/go/net/fd_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris package net @@ -75,29 +75,47 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr) error { if err := fd.pd.PrepareWrite(); err != nil { return err } - for { - err := syscall.Connect(fd.sysfd, ra) - if err == nil || err == syscall.EISCONN { - break - } - + switch err := syscall.Connect(fd.sysfd, ra); err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + case nil, syscall.EISCONN: + return nil + case syscall.EINVAL: // On Solaris we can see EINVAL if the socket has // already been accepted and closed by the server. // Treat this as a successful connection--writes to // the socket will see EOF. For details and a test // case in C see http://golang.org/issue/6828. - if runtime.GOOS == "solaris" && err == syscall.EINVAL { - break + if runtime.GOOS == "solaris" { + return nil } - - if err != syscall.EINPROGRESS && err != syscall.EALREADY && err != syscall.EINTR { + fallthrough + default: + return err + } + for { + // Performing multiple connect system calls on a + // non-blocking socket under Unix variants does not + // necessarily result in earlier errors being + // returned. Instead, once runtime-integrated network + // poller tells us that the socket is ready, get the + // SO_ERROR socket option to see if the connection + // succeeded or failed. See issue 7474 for further + // details. + if err := fd.pd.WaitWrite(); err != nil { return err } - if err = fd.pd.WaitWrite(); err != nil { + nerr, err := syscall.GetsockoptInt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) + if err != nil { + return err + } + switch err := syscall.Errno(nerr); err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + case syscall.Errno(0), syscall.EISCONN: + return nil + default: return err } } - return nil } func (fd *netFD) destroy() { @@ -190,11 +208,11 @@ func (fd *netFD) shutdown(how int) error { return nil } -func (fd *netFD) CloseRead() error { +func (fd *netFD) closeRead() error { return fd.shutdown(syscall.SHUT_RD) } -func (fd *netFD) CloseWrite() error { +func (fd *netFD) closeWrite() error { return fd.shutdown(syscall.SHUT_WR) } @@ -225,7 +243,7 @@ func (fd *netFD) Read(p []byte) (n int, err error) { return } -func (fd *netFD) ReadFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { if err := fd.readLock(); err != nil { return 0, nil, err } @@ -252,7 +270,7 @@ func (fd *netFD) ReadFrom(p []byte) (n int, sa syscall.Sockaddr, err error) { return } -func (fd *netFD) ReadMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { if err := fd.readLock(); err != nil { return 0, 0, 0, nil, err } @@ -323,7 +341,7 @@ func (fd *netFD) Write(p []byte) (nn int, err error) { return nn, err } -func (fd *netFD) WriteTo(p []byte, sa syscall.Sockaddr) (n int, err error) { +func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) { if err := fd.writeLock(); err != nil { return 0, err } @@ -348,7 +366,7 @@ func (fd *netFD) WriteTo(p []byte, sa syscall.Sockaddr) (n int, err error) { return } -func (fd *netFD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { +func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { if err := fd.writeLock(); err != nil { return 0, 0, err } @@ -357,7 +375,7 @@ func (fd *netFD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob return 0, 0, &OpError{"write", fd.net, fd.raddr, err} } for { - err = syscall.Sendmsg(fd.sysfd, p, oob, sa, 0) + n, err = syscall.SendmsgN(fd.sysfd, p, oob, sa, 0) if err == syscall.EAGAIN { if err = fd.pd.WaitWrite(); err == nil { continue @@ -366,7 +384,6 @@ func (fd *netFD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oob break } if err == nil { - n = len(p) oobn = len(oob) } else { err = &OpError{"write", fd.net, fd.raddr, err} @@ -465,7 +482,6 @@ func dupCloseOnExecOld(fd int) (newfd int, err error) { func (fd *netFD) dup() (f *os.File, err error) { ns, err := dupCloseOnExec(fd.sysfd) if err != nil { - syscall.ForkLock.RUnlock() return nil, &OpError{"dup", fd.net, fd.laddr, err} } diff --git a/libgo/go/net/fd_unix_test.go b/libgo/go/net/fd_unix_test.go index 65d3e69a764..fe8e8ff6a88 100644 --- a/libgo/go/net/fd_unix_test.go +++ b/libgo/go/net/fd_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd solaris package net diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go index 0f8d6de5b54..a1f6bc5f814 100644 --- a/libgo/go/net/fd_windows.go +++ b/libgo/go/net/fd_windows.go @@ -431,11 +431,11 @@ func (fd *netFD) shutdown(how int) error { return nil } -func (fd *netFD) CloseRead() error { +func (fd *netFD) closeRead() error { return fd.shutdown(syscall.SHUT_RD) } -func (fd *netFD) CloseWrite() error { +func (fd *netFD) closeWrite() error { return fd.shutdown(syscall.SHUT_WR) } @@ -458,7 +458,7 @@ func (fd *netFD) Read(buf []byte) (int, error) { return n, err } -func (fd *netFD) ReadFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readFrom(buf []byte) (n int, sa syscall.Sockaddr, err error) { if len(buf) == 0 { return 0, nil, nil } @@ -497,7 +497,7 @@ func (fd *netFD) Write(buf []byte) (int, error) { }) } -func (fd *netFD) WriteTo(buf []byte, sa syscall.Sockaddr) (int, error) { +func (fd *netFD) writeTo(buf []byte, sa syscall.Sockaddr) (int, error) { if len(buf) == 0 { return 0, nil } @@ -628,10 +628,10 @@ func (fd *netFD) dup() (*os.File, error) { var errNoSupport = errors.New("address family not supported") -func (fd *netFD) ReadMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { +func (fd *netFD) readMsg(p []byte, oob []byte) (n, oobn, flags int, sa syscall.Sockaddr, err error) { return 0, 0, 0, nil, errNoSupport } -func (fd *netFD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { +func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) { return 0, 0, errNoSupport } diff --git a/libgo/go/net/file_test.go b/libgo/go/net/file_test.go index e4615b74fc3..d81bca78249 100644 --- a/libgo/go/net/file_test.go +++ b/libgo/go/net/file_test.go @@ -181,7 +181,7 @@ var filePacketConnTests = []struct { func TestFilePacketConn(t *testing.T) { switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } diff --git a/libgo/go/net/file_unix.go b/libgo/go/net/file_unix.go index 38ae47f7847..07b3ecf6263 100644 --- a/libgo/go/net/file_unix.go +++ b/libgo/go/net/file_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris package net diff --git a/libgo/go/net/http/cgi/host.go b/libgo/go/net/http/cgi/host.go index d27cc4dc9a8..ec95a972c1a 100644 --- a/libgo/go/net/http/cgi/host.go +++ b/libgo/go/net/http/cgi/host.go @@ -214,12 +214,17 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { internalError(err) return } + if hook := testHookStartProcess; hook != nil { + hook(cmd.Process) + } defer cmd.Wait() defer stdoutRead.Close() linebody := bufio.NewReaderSize(stdoutRead, 1024) headers := make(http.Header) statusCode := 0 + headerLines := 0 + sawBlankLine := false for { line, isPrefix, err := linebody.ReadLine() if isPrefix { @@ -236,8 +241,10 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } if len(line) == 0 { + sawBlankLine = true break } + headerLines++ parts := strings.SplitN(string(line), ":", 2) if len(parts) < 2 { h.printf("cgi: bogus header line: %s", string(line)) @@ -263,6 +270,11 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { headers.Add(header, val) } } + if headerLines == 0 || !sawBlankLine { + rw.WriteHeader(http.StatusInternalServerError) + h.printf("cgi: no headers") + return + } if loc := headers.Get("Location"); loc != "" { if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil { @@ -274,6 +286,12 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } + if statusCode == 0 && headers.Get("Content-Type") == "" { + rw.WriteHeader(http.StatusInternalServerError) + h.printf("cgi: missing required Content-Type in headers") + return + } + if statusCode == 0 { statusCode = http.StatusOK } @@ -292,6 +310,13 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { _, err = io.Copy(rw, linebody) if err != nil { h.printf("cgi: copy error: %v", err) + // And kill the child CGI process so we don't hang on + // the deferred cmd.Wait above if the error was just + // the client (rw) going away. If it was a read error + // (because the child died itself), then the extra + // kill of an already-dead process is harmless (the PID + // won't be reused until the Wait above). + cmd.Process.Kill() } } @@ -348,3 +373,5 @@ func upperCaseAndUnderscore(r rune) rune { // TODO: other transformations in spec or practice? return r } + +var testHookStartProcess func(*os.Process) // nil except for some tests diff --git a/libgo/go/net/http/cgi/matryoshka_test.go b/libgo/go/net/http/cgi/matryoshka_test.go index e1a78c8f62f..18c4803e71b 100644 --- a/libgo/go/net/http/cgi/matryoshka_test.go +++ b/libgo/go/net/http/cgi/matryoshka_test.go @@ -9,15 +9,25 @@ package cgi import ( + "bytes" + "errors" "fmt" + "io" "net/http" + "net/http/httptest" "os" + "runtime" "testing" + "time" ) // This test is a CGI host (testing host.go) that runs its own binary // as a child process testing the other half of CGI (child.go). func TestHostingOurselves(t *testing.T) { + if runtime.GOOS == "nacl" { + t.Skip("skipping on nacl") + } + h := &Handler{ Path: os.Args[0], Root: "/test.go", @@ -51,8 +61,88 @@ func TestHostingOurselves(t *testing.T) { } } -// Test that a child handler only writing headers works. +type customWriterRecorder struct { + w io.Writer + *httptest.ResponseRecorder +} + +func (r *customWriterRecorder) Write(p []byte) (n int, err error) { + return r.w.Write(p) +} + +type limitWriter struct { + w io.Writer + n int +} + +func (w *limitWriter) Write(p []byte) (n int, err error) { + if len(p) > w.n { + p = p[:w.n] + } + if len(p) > 0 { + n, err = w.w.Write(p) + w.n -= n + } + if w.n == 0 { + err = errors.New("past write limit") + } + return +} + +// If there's an error copying the child's output to the parent, test +// that we kill the child. +func TestKillChildAfterCopyError(t *testing.T) { + if runtime.GOOS == "nacl" { + t.Skip("skipping on nacl") + } + + defer func() { testHookStartProcess = nil }() + proc := make(chan *os.Process, 1) + testHookStartProcess = func(p *os.Process) { + proc <- p + } + + h := &Handler{ + Path: os.Args[0], + Root: "/test.go", + Args: []string{"-test.run=TestBeChildCGIProcess"}, + } + req, _ := http.NewRequest("GET", "http://example.com/test.cgi?write-forever=1", nil) + rec := httptest.NewRecorder() + var out bytes.Buffer + const writeLen = 50 << 10 + rw := &customWriterRecorder{&limitWriter{&out, writeLen}, rec} + + donec := make(chan bool, 1) + go func() { + h.ServeHTTP(rw, req) + donec <- true + }() + + select { + case <-donec: + if out.Len() != writeLen || out.Bytes()[0] != 'a' { + t.Errorf("unexpected output: %q", out.Bytes()) + } + case <-time.After(5 * time.Second): + t.Errorf("timeout. ServeHTTP hung and didn't kill the child process?") + select { + case p := <-proc: + p.Kill() + t.Logf("killed process") + default: + t.Logf("didn't kill process") + } + } +} + +// Test that a child handler writing only headers works. +// golang.org/issue/7196 func TestChildOnlyHeaders(t *testing.T) { + if runtime.GOOS == "nacl" { + t.Skip("skipping on nacl") + } + h := &Handler{ Path: os.Args[0], Root: "/test.go", @@ -67,18 +157,63 @@ func TestChildOnlyHeaders(t *testing.T) { } } +// golang.org/issue/7198 +func Test500WithNoHeaders(t *testing.T) { want500Test(t, "/immediate-disconnect") } +func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") } +func Test500WithEmptyHeaders(t *testing.T) { want500Test(t, "/empty-headers") } + +func want500Test(t *testing.T, path string) { + h := &Handler{ + Path: os.Args[0], + Root: "/test.go", + Args: []string{"-test.run=TestBeChildCGIProcess"}, + } + expectedMap := map[string]string{ + "_body": "", + } + replay := runCgiTest(t, h, "GET "+path+" HTTP/1.0\nHost: example.com\n\n", expectedMap) + if replay.Code != 500 { + t.Errorf("Got code %d; want 500", replay.Code) + } +} + +type neverEnding byte + +func (b neverEnding) Read(p []byte) (n int, err error) { + for i := range p { + p[i] = byte(b) + } + return len(p), nil +} + // Note: not actually a test. func TestBeChildCGIProcess(t *testing.T) { if os.Getenv("REQUEST_METHOD") == "" { // Not in a CGI environment; skipping test. return } + switch os.Getenv("REQUEST_URI") { + case "/immediate-disconnect": + os.Exit(0) + case "/no-content-type": + fmt.Printf("Content-Length: 6\n\nHello\n") + os.Exit(0) + case "/empty-headers": + fmt.Printf("\nHello") + os.Exit(0) + } Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("X-Test-Header", "X-Test-Value") req.ParseForm() if req.FormValue("no-body") == "1" { return } + if req.FormValue("write-forever") == "1" { + io.Copy(rw, neverEnding('a')) + for { + time.Sleep(5 * time.Second) // hang forever, until killed + } + } fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n") for k, vv := range req.Form { for _, v := range vv { diff --git a/libgo/go/net/http/client.go b/libgo/go/net/http/client.go index 22f2e865cf7..a5a3abe6138 100644 --- a/libgo/go/net/http/client.go +++ b/libgo/go/net/http/client.go @@ -14,9 +14,12 @@ import ( "errors" "fmt" "io" + "io/ioutil" "log" "net/url" "strings" + "sync" + "time" ) // A Client is an HTTP client. Its zero value (DefaultClient) is a @@ -52,6 +55,20 @@ type Client struct { // If Jar is nil, cookies are not sent in requests and ignored // in responses. Jar CookieJar + + // Timeout specifies a time limit for requests made by this + // Client. The timeout includes connection time, any + // redirects, and reading the response body. The timer remains + // running after Get, Head, Post, or Do return and will + // interrupt reading of the Response.Body. + // + // A Timeout of zero means no timeout. + // + // The Client's Transport must support the CancelRequest + // method or Client will return errors when attempting to make + // a request with Get, Head, Post, or Do. Client's default + // Transport (DefaultTransport) supports CancelRequest. + Timeout time.Duration } // DefaultClient is the default Client and is used by Get, Head, and Post. @@ -74,8 +91,9 @@ type RoundTripper interface { // authentication, or cookies. // // RoundTrip should not modify the request, except for - // consuming and closing the Body. The request's URL and - // Header fields are guaranteed to be initialized. + // consuming and closing the Body, including on errors. The + // request's URL and Header fields are guaranteed to be + // initialized. RoundTrip(*Request) (*Response, error) } @@ -97,7 +115,7 @@ func (c *Client) send(req *Request) (*Response, error) { req.AddCookie(cookie) } } - resp, err := send(req, c.Transport) + resp, err := send(req, c.transport()) if err != nil { return nil, err } @@ -123,6 +141,9 @@ func (c *Client) send(req *Request) (*Response, error) { // (typically Transport) may not be able to re-use a persistent TCP // connection to the server for a subsequent "keep-alive" request. // +// The request Body, if non-nil, will be closed by the underlying +// Transport, even on errors. +// // Generally Get, Post, or PostForm will be used instead of Do. func (c *Client) Do(req *Request) (resp *Response, err error) { if req.Method == "GET" || req.Method == "HEAD" { @@ -134,22 +155,28 @@ func (c *Client) Do(req *Request) (resp *Response, err error) { return c.send(req) } +func (c *Client) transport() RoundTripper { + if c.Transport != nil { + return c.Transport + } + return DefaultTransport +} + // send issues an HTTP request. // Caller should close resp.Body when done reading from it. func send(req *Request, t RoundTripper) (resp *Response, err error) { if t == nil { - t = DefaultTransport - if t == nil { - err = errors.New("http: no Client.Transport or DefaultTransport") - return - } + req.closeBody() + return nil, errors.New("http: no Client.Transport or DefaultTransport") } if req.URL == nil { + req.closeBody() return nil, errors.New("http: nil Request.URL") } if req.RequestURI != "" { + req.closeBody() return nil, errors.New("http: Request.RequestURI can't be set in client requests.") } @@ -257,21 +284,40 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo var via []*Request if ireq.URL == nil { + ireq.closeBody() return nil, errors.New("http: nil Request.URL") } + var reqmu sync.Mutex // guards req req := ireq + + var timer *time.Timer + if c.Timeout > 0 { + type canceler interface { + CancelRequest(*Request) + } + tr, ok := c.transport().(canceler) + if !ok { + return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport()) + } + timer = time.AfterFunc(c.Timeout, func() { + reqmu.Lock() + defer reqmu.Unlock() + tr.CancelRequest(req) + }) + } + urlStr := "" // next relative or absolute URL to fetch (after first request) redirectFailed := false for redirect := 0; ; redirect++ { if redirect != 0 { - req = new(Request) - req.Method = ireq.Method + nreq := new(Request) + nreq.Method = ireq.Method if ireq.Method == "POST" || ireq.Method == "PUT" { - req.Method = "GET" + nreq.Method = "GET" } - req.Header = make(Header) - req.URL, err = base.Parse(urlStr) + nreq.Header = make(Header) + nreq.URL, err = base.Parse(urlStr) if err != nil { break } @@ -279,15 +325,18 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo // Add the Referer header. lastReq := via[len(via)-1] if lastReq.URL.Scheme != "https" { - req.Header.Set("Referer", lastReq.URL.String()) + nreq.Header.Set("Referer", lastReq.URL.String()) } - err = redirectChecker(req, via) + err = redirectChecker(nreq, via) if err != nil { redirectFailed = true break } } + reqmu.Lock() + req = nreq + reqmu.Unlock() } urlStr = req.URL.String() @@ -296,6 +345,12 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo } if shouldRedirect(resp.StatusCode) { + // Read the body if small so underlying TCP connection will be re-used. + // No need to check for errors: if it fails, Transport won't reuse it anyway. + const maxBodySlurpSize = 2 << 10 + if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize { + io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize) + } resp.Body.Close() if urlStr = resp.Header.Get("Location"); urlStr == "" { err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode)) @@ -305,7 +360,10 @@ func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bo via = append(via, req) continue } - return + if timer != nil { + resp.Body = &cancelTimerBody{timer, resp.Body} + } + return resp, nil } method := ireq.Method @@ -349,7 +407,7 @@ func Post(url string, bodyType string, body io.Reader) (resp *Response, err erro // Caller should close resp.Body when done reading from it. // // If the provided body is also an io.Closer, it is closed after the -// body is successfully written to the server. +// request. func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { req, err := NewRequest("POST", url, body) if err != nil { @@ -408,3 +466,22 @@ func (c *Client) Head(url string) (resp *Response, err error) { } return c.doFollowingRedirects(req, shouldRedirectGet) } + +type cancelTimerBody struct { + t *time.Timer + rc io.ReadCloser +} + +func (b *cancelTimerBody) Read(p []byte) (n int, err error) { + n, err = b.rc.Read(p) + if err == io.EOF { + b.t.Stop() + } + return +} + +func (b *cancelTimerBody) Close() error { + err := b.rc.Close() + b.t.Stop() + return err +} diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index e5ad39c7741..6392c1baf39 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -15,14 +15,18 @@ import ( "fmt" "io" "io/ioutil" + "log" "net" . "net/http" "net/http/httptest" "net/url" + "reflect" + "sort" "strconv" "strings" "sync" "testing" + "time" ) var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) { @@ -54,6 +58,13 @@ func pedanticReadAll(r io.Reader) (b []byte, err error) { } } +type chanWriter chan string + +func (w chanWriter) Write(p []byte) (n int, err error) { + w <- string(p) + return len(p), nil +} + func TestClient(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(robotsTxtHandler) @@ -564,6 +575,8 @@ func TestClientInsecureTransport(t *testing.T) { ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { w.Write([]byte("Hello")) })) + errc := make(chanWriter, 10) // but only expecting 1 + ts.Config.ErrorLog = log.New(errc, "", 0) defer ts.Close() // TODO(bradfitz): add tests for skipping hostname checks too? @@ -585,6 +598,16 @@ func TestClientInsecureTransport(t *testing.T) { res.Body.Close() } } + + select { + case v := <-errc: + if !strings.Contains(v, "TLS handshake error") { + t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v) + } + case <-time.After(5 * time.Second): + t.Errorf("timeout waiting for logged error") + } + } func TestClientErrorWithRequestURI(t *testing.T) { @@ -635,6 +658,8 @@ func TestClientWithIncorrectTLSServerName(t *testing.T) { defer afterTest(t) ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) defer ts.Close() + errc := make(chanWriter, 10) // but only expecting 1 + ts.Config.ErrorLog = log.New(errc, "", 0) trans := newTLSTransport(t, ts) trans.TLSClientConfig.ServerName = "badserver" @@ -646,6 +671,14 @@ func TestClientWithIncorrectTLSServerName(t *testing.T) { if !strings.Contains(err.Error(), "127.0.0.1") || !strings.Contains(err.Error(), "badserver") { t.Errorf("wanted error mentioning 127.0.0.1 and badserver; got error: %v", err) } + select { + case v := <-errc: + if !strings.Contains(v, "TLS handshake error") { + t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v) + } + case <-time.After(5 * time.Second): + t.Errorf("timeout waiting for logged error") + } } // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName @@ -678,6 +711,33 @@ func TestTransportUsesTLSConfigServerName(t *testing.T) { res.Body.Close() } +func TestResponseSetsTLSConnectionState(t *testing.T) { + defer afterTest(t) + ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Write([]byte("Hello")) + })) + defer ts.Close() + + tr := newTLSTransport(t, ts) + tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA} + tr.Dial = func(netw, addr string) (net.Conn, error) { + return net.Dial(netw, ts.Listener.Addr().String()) + } + defer tr.CloseIdleConnections() + c := &Client{Transport: tr} + res, err := c.Get("https://example.com/") + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + if res.TLS == nil { + t.Fatal("Response didn't set TLS Connection State.") + } + if got, want := res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA; got != want { + t.Errorf("TLS Cipher Suite = %d; want %d", got, want) + } +} + // Verify Response.ContentLength is populated. http://golang.org/issue/4126 func TestClientHeadContentLength(t *testing.T) { defer afterTest(t) @@ -781,3 +841,198 @@ func TestBasicAuth(t *testing.T) { t.Errorf("Invalid auth %q", auth) } } + +func TestClientTimeout(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + defer afterTest(t) + sawRoot := make(chan bool, 1) + sawSlow := make(chan bool, 1) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + if r.URL.Path == "/" { + sawRoot <- true + Redirect(w, r, "/slow", StatusFound) + return + } + if r.URL.Path == "/slow" { + w.Write([]byte("Hello")) + w.(Flusher).Flush() + sawSlow <- true + time.Sleep(2 * time.Second) + return + } + })) + defer ts.Close() + const timeout = 500 * time.Millisecond + c := &Client{ + Timeout: timeout, + } + + res, err := c.Get(ts.URL) + if err != nil { + t.Fatal(err) + } + + select { + case <-sawRoot: + // good. + default: + t.Fatal("handler never got / request") + } + + select { + case <-sawSlow: + // good. + default: + t.Fatal("handler never got /slow request") + } + + errc := make(chan error, 1) + go func() { + _, err := ioutil.ReadAll(res.Body) + errc <- err + res.Body.Close() + }() + + const failTime = timeout * 2 + select { + case err := <-errc: + if err == nil { + t.Error("expected error from ReadAll") + } + // Expected error. + case <-time.After(failTime): + t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout) + } +} + +func TestClientRedirectEatsBody(t *testing.T) { + defer afterTest(t) + saw := make(chan string, 2) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + saw <- r.RemoteAddr + if r.URL.Path == "/" { + Redirect(w, r, "/foo", StatusFound) // which includes a body + } + })) + defer ts.Close() + + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + _, err = ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + res.Body.Close() + + var first string + select { + case first = <-saw: + default: + t.Fatal("server didn't see a request") + } + + var second string + select { + case second = <-saw: + default: + t.Fatal("server didn't see a second request") + } + + if first != second { + t.Fatal("server saw different client ports before & after the redirect") + } +} + +// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF. +type eofReaderFunc func() + +func (f eofReaderFunc) Read(p []byte) (n int, err error) { + f() + return 0, io.EOF +} + +func TestClientTrailers(t *testing.T) { + defer afterTest(t) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Connection", "close") + w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B") + w.Header().Add("Trailer", "Server-Trailer-C") + + var decl []string + for k := range r.Trailer { + decl = append(decl, k) + } + sort.Strings(decl) + + slurp, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Server reading request body: %v", err) + } + if string(slurp) != "foo" { + t.Errorf("Server read request body %q; want foo", slurp) + } + if r.Trailer == nil { + io.WriteString(w, "nil Trailer") + } else { + fmt.Fprintf(w, "decl: %v, vals: %s, %s", + decl, + r.Trailer.Get("Client-Trailer-A"), + r.Trailer.Get("Client-Trailer-B")) + } + + // TODO: golang.org/issue/7759: there's no way yet for + // the server to set trailers without hijacking, so do + // that for now, just to test the client. Later, in + // Go 1.4, it should be implicit that any mutations + // to w.Header() after the initial write are the + // trailers to be sent, if and only if they were + // previously declared with w.Header().Set("Trailer", + // ..keys..) + w.(Flusher).Flush() + conn, buf, _ := w.(Hijacker).Hijack() + t := Header{} + t.Set("Server-Trailer-A", "valuea") + t.Set("Server-Trailer-C", "valuec") // skipping B + buf.WriteString("0\r\n") // eof + t.Write(buf) + buf.WriteString("\r\n") // end of trailers + buf.Flush() + conn.Close() + })) + defer ts.Close() + + var req *Request + req, _ = NewRequest("POST", ts.URL, io.MultiReader( + eofReaderFunc(func() { + req.Trailer["Client-Trailer-A"] = []string{"valuea"} + }), + strings.NewReader("foo"), + eofReaderFunc(func() { + req.Trailer["Client-Trailer-B"] = []string{"valueb"} + }), + )) + req.Trailer = Header{ + "Client-Trailer-A": nil, // to be set later + "Client-Trailer-B": nil, // to be set later + } + req.ContentLength = -1 + res, err := DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil { + t.Error(err) + } + want := Header{ + "Server-Trailer-A": []string{"valuea"}, + "Server-Trailer-B": nil, + "Server-Trailer-C": []string{"valuec"}, + } + if !reflect.DeepEqual(res.Trailer, want) { + t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want) + } +} diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index a1759214f38..dc60ba87f5f 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -76,11 +76,7 @@ func readSetCookies(h Header) []*Cookie { attr, val = attr[:j], attr[j+1:] } lowerAttr := strings.ToLower(attr) - parseCookieValueFn := parseCookieValue - if lowerAttr == "expires" { - parseCookieValueFn = parseCookieExpiresValue - } - val, success = parseCookieValueFn(val) + val, success = parseCookieValue(val) if !success { c.Unparsed = append(c.Unparsed, parts[i]) continue @@ -298,12 +294,23 @@ func sanitizeCookieName(n string) string { // ; US-ASCII characters excluding CTLs, // ; whitespace DQUOTE, comma, semicolon, // ; and backslash +// We loosen this as spaces and commas are common in cookie values +// but we produce a quoted cookie-value in when value starts or ends +// with a comma or space. +// See http://golang.org/issue/7243 for the discussion. func sanitizeCookieValue(v string) string { - return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) + v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) + if len(v) == 0 { + return v + } + if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' { + return `"` + v + `"` + } + return v } func validCookieValueByte(b byte) bool { - return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\' + return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' } // path-av = "Path=" path-value @@ -338,38 +345,13 @@ func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { return string(buf) } -func unquoteCookieValue(v string) string { - if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { - return v[1 : len(v)-1] - } - return v -} - -func isCookieByte(c byte) bool { - switch { - case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, - 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: - return true - } - return false -} - -func isCookieExpiresByte(c byte) (ok bool) { - return isCookieByte(c) || c == ',' || c == ' ' -} - func parseCookieValue(raw string) (string, bool) { - return parseCookieValueUsing(raw, isCookieByte) -} - -func parseCookieExpiresValue(raw string) (string, bool) { - return parseCookieValueUsing(raw, isCookieExpiresByte) -} - -func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { - raw = unquoteCookieValue(raw) + // Strip the quotes, if present. + if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { + raw = raw[1 : len(raw)-1] + } for i := 0; i < len(raw); i++ { - if !validByte(raw[i]) { + if !validCookieValueByte(raw[i]) { return "", false } } diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 1aa9d49d96e..f78f37299f4 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -52,6 +52,44 @@ var writeSetCookiesTests = []struct { &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, "cookie-8=eight", }, + // The "special" cookies have values containing commas or spaces which + // are disallowed by RFC 6265 but are common in the wild. + { + &Cookie{Name: "special-1", Value: "a z"}, + `special-1=a z`, + }, + { + &Cookie{Name: "special-2", Value: " z"}, + `special-2=" z"`, + }, + { + &Cookie{Name: "special-3", Value: "a "}, + `special-3="a "`, + }, + { + &Cookie{Name: "special-4", Value: " "}, + `special-4=" "`, + }, + { + &Cookie{Name: "special-5", Value: "a,z"}, + `special-5=a,z`, + }, + { + &Cookie{Name: "special-6", Value: ",z"}, + `special-6=",z"`, + }, + { + &Cookie{Name: "special-7", Value: "a,"}, + `special-7="a,"`, + }, + { + &Cookie{Name: "special-8", Value: ","}, + `special-8=","`, + }, + { + &Cookie{Name: "empty-value", Value: ""}, + `empty-value=`, + }, } func TestWriteSetCookies(t *testing.T) { @@ -178,6 +216,40 @@ var readSetCookiesTests = []struct { Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly", }}, }, + // Make sure we can properly read back the Set-Cookie headers we create + // for values containing spaces or commas: + { + Header{"Set-Cookie": {`special-1=a z`}}, + []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}}, + }, + { + Header{"Set-Cookie": {`special-2=" z"`}}, + []*Cookie{{Name: "special-2", Value: " z", Raw: `special-2=" z"`}}, + }, + { + Header{"Set-Cookie": {`special-3="a "`}}, + []*Cookie{{Name: "special-3", Value: "a ", Raw: `special-3="a "`}}, + }, + { + Header{"Set-Cookie": {`special-4=" "`}}, + []*Cookie{{Name: "special-4", Value: " ", Raw: `special-4=" "`}}, + }, + { + Header{"Set-Cookie": {`special-5=a,z`}}, + []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}}, + }, + { + Header{"Set-Cookie": {`special-6=",z"`}}, + []*Cookie{{Name: "special-6", Value: ",z", Raw: `special-6=",z"`}}, + }, + { + Header{"Set-Cookie": {`special-7=a,`}}, + []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}}, + }, + { + Header{"Set-Cookie": {`special-8=","`}}, + []*Cookie{{Name: "special-8", Value: ",", Raw: `special-8=","`}}, + }, // TODO(bradfitz): users have reported seeing this in the // wild, but do browsers handle it? RFC 6265 just says "don't @@ -264,9 +336,14 @@ func TestCookieSanitizeValue(t *testing.T) { in, want string }{ {"foo", "foo"}, - {"foo bar", "foobar"}, + {"foo;bar", "foobar"}, + {"foo\\bar", "foobar"}, + {"foo\"bar", "foobar"}, {"\x00\x7e\x7f\x80", "\x7e"}, {`"withquotes"`, "withquotes"}, + {"a z", "a z"}, + {" z", `" z"`}, + {"a ", `"a "`}, } for _, tt := range tests { if got := sanitizeCookieValue(tt.in); got != tt.want { diff --git a/libgo/go/net/http/export_test.go b/libgo/go/net/http/export_test.go index 8074df5bbde..960563b2409 100644 --- a/libgo/go/net/http/export_test.go +++ b/libgo/go/net/http/export_test.go @@ -21,7 +21,7 @@ var ExportAppendTime = appendTime func (t *Transport) NumPendingRequestsForTesting() int { t.reqMu.Lock() defer t.reqMu.Unlock() - return len(t.reqConn) + return len(t.reqCanceler) } func (t *Transport) IdleConnKeysForTesting() (keys []string) { diff --git a/libgo/go/net/http/fcgi/child.go b/libgo/go/net/http/fcgi/child.go index 60b794e0775..a3beaa33a86 100644 --- a/libgo/go/net/http/fcgi/child.go +++ b/libgo/go/net/http/fcgi/child.go @@ -16,6 +16,7 @@ import ( "net/http/cgi" "os" "strings" + "sync" "time" ) @@ -126,8 +127,10 @@ func (r *response) Close() error { } type child struct { - conn *conn - handler http.Handler + conn *conn + handler http.Handler + + mu sync.Mutex // protects requests: requests map[uint16]*request // keyed by request ID } @@ -157,7 +160,9 @@ var errCloseConn = errors.New("fcgi: connection should be closed") var emptyBody = ioutil.NopCloser(strings.NewReader("")) func (c *child) handleRecord(rec *record) error { + c.mu.Lock() req, ok := c.requests[rec.h.Id] + c.mu.Unlock() if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues { // The spec says to ignore unknown request IDs. return nil @@ -179,7 +184,10 @@ func (c *child) handleRecord(rec *record) error { c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole) return nil } - c.requests[rec.h.Id] = newRequest(rec.h.Id, br.flags) + req = newRequest(rec.h.Id, br.flags) + c.mu.Lock() + c.requests[rec.h.Id] = req + c.mu.Unlock() return nil case typeParams: // NOTE(eds): Technically a key-value pair can straddle the boundary @@ -220,7 +228,9 @@ func (c *child) handleRecord(rec *record) error { return nil case typeAbortRequest: println("abort") + c.mu.Lock() delete(c.requests, rec.h.Id) + c.mu.Unlock() c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete) if !req.keepConn { // connection will close upon return @@ -247,6 +257,9 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) { c.handler.ServeHTTP(r, httpReq) } r.Close() + c.mu.Lock() + delete(c.requests, req.reqId) + c.mu.Unlock() c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete) // Consume the entire body, so the host isn't still writing to diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go index 9df5cc48189..8576cf844a3 100644 --- a/libgo/go/net/http/fs.go +++ b/libgo/go/net/http/fs.go @@ -527,7 +527,7 @@ func (w *countingWriter) Write(p []byte) (n int, err error) { return len(p), nil } -// rangesMIMESize returns the nunber of bytes it takes to encode the +// rangesMIMESize returns the number of bytes it takes to encode the // provided ranges as a multipart response. func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) { var w countingWriter diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go index de62bef5525..153b94370f8 100644 --- a/libgo/go/net/http/header.go +++ b/libgo/go/net/http/header.go @@ -13,6 +13,8 @@ import ( "time" ) +var raceEnabled = false // set by race.go + // A Header represents the key-value pairs in an HTTP header. type Header map[string][]string diff --git a/libgo/go/net/http/header_test.go b/libgo/go/net/http/header_test.go index 2c896c5ad23..299576ba8cf 100644 --- a/libgo/go/net/http/header_test.go +++ b/libgo/go/net/http/header_test.go @@ -192,9 +192,12 @@ func BenchmarkHeaderWriteSubset(b *testing.B) { } } -func TestHeaderWriteSubsetMallocs(t *testing.T) { +func TestHeaderWriteSubsetAllocs(t *testing.T) { if testing.Short() { - t.Skip("skipping malloc count in short mode") + t.Skip("skipping alloc test in short mode") + } + if raceEnabled { + t.Skip("skipping test under race detector") } t.Skip("Skipping alloc count test on gccgo") if runtime.GOMAXPROCS(0) > 1 { @@ -205,6 +208,6 @@ func TestHeaderWriteSubsetMallocs(t *testing.T) { testHeader.WriteSubset(&buf, nil) }) if n > 0 { - t.Errorf("mallocs = %g; want 0", n) + t.Errorf("allocs = %g; want 0", n) } } diff --git a/libgo/go/net/http/httptest/server_test.go b/libgo/go/net/http/httptest/server_test.go index 500a9f0b800..501cc8a9995 100644 --- a/libgo/go/net/http/httptest/server_test.go +++ b/libgo/go/net/http/httptest/server_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "testing" + "time" ) func TestServer(t *testing.T) { @@ -27,3 +28,25 @@ func TestServer(t *testing.T) { t.Errorf("got %q, want hello", string(got)) } } + +func TestIssue7264(t *testing.T) { + for i := 0; i < 1000; i++ { + func() { + inHandler := make(chan bool, 1) + ts := NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + inHandler <- true + })) + defer ts.Close() + tr := &http.Transport{ + ResponseHeaderTimeout: time.Nanosecond, + } + defer tr.CloseIdleConnections() + c := &http.Client{Transport: tr} + res, err := c.Get(ts.URL) + <-inHandler + if err == nil { + res.Body.Close() + } + }() + } +} diff --git a/libgo/go/net/http/httputil/dump.go b/libgo/go/net/http/httputil/dump.go index ab1eab21bc6..2a7a413d01a 100644 --- a/libgo/go/net/http/httputil/dump.go +++ b/libgo/go/net/http/httputil/dump.go @@ -7,6 +7,7 @@ package httputil import ( "bufio" "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -106,6 +107,7 @@ func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil }, } + defer t.CloseIdleConnections() _, err := t.RoundTrip(reqSend) @@ -230,14 +232,31 @@ func DumpRequest(req *http.Request, body bool) (dump []byte, err error) { return } +// errNoBody is a sentinel error value used by failureToReadBody so we can detect +// that the lack of body was intentional. +var errNoBody = errors.New("sentinel error value") + +// failureToReadBody is a io.ReadCloser that just returns errNoBody on +// Read. It's swapped in when we don't actually want to consume the +// body, but need a non-nil one, and want to distinguish the error +// from reading the dummy body. +type failureToReadBody struct{} + +func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } +func (failureToReadBody) Close() error { return nil } + +var emptyBody = ioutil.NopCloser(strings.NewReader("")) + // DumpResponse is like DumpRequest but dumps a response. func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) { var b bytes.Buffer save := resp.Body savecl := resp.ContentLength - if !body || resp.Body == nil { - resp.Body = nil - resp.ContentLength = 0 + + if !body { + resp.Body = failureToReadBody{} + } else if resp.Body == nil { + resp.Body = emptyBody } else { save, resp.Body, err = drainBody(resp.Body) if err != nil { @@ -245,11 +264,13 @@ func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) { } } err = resp.Write(&b) + if err == errNoBody { + err = nil + } resp.Body = save resp.ContentLength = savecl if err != nil { - return + return nil, err } - dump = b.Bytes() - return + return b.Bytes(), nil } diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go index a1dbfc39d6b..e1ffb3935ac 100644 --- a/libgo/go/net/http/httputil/dump_test.go +++ b/libgo/go/net/http/httputil/dump_test.go @@ -11,6 +11,8 @@ import ( "io/ioutil" "net/http" "net/url" + "runtime" + "strings" "testing" ) @@ -112,6 +114,7 @@ var dumpTests = []dumpTest{ } func TestDumpRequest(t *testing.T) { + numg0 := runtime.NumGoroutine() for i, tt := range dumpTests { setBody := func() { if tt.Body == nil { @@ -155,6 +158,9 @@ func TestDumpRequest(t *testing.T) { } } } + if dg := runtime.NumGoroutine() - numg0; dg > 4 { + t.Errorf("Unexpectedly large number of new goroutines: %d new", dg) + } } func chunk(s string) string { @@ -176,3 +182,82 @@ func mustNewRequest(method, url string, body io.Reader) *http.Request { } return req } + +var dumpResTests = []struct { + res *http.Response + body bool + want string +}{ + { + res: &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: 50, + Header: http.Header{ + "Foo": []string{"Bar"}, + }, + Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used + }, + body: false, // to verify we see 50, not empty or 3. + want: `HTTP/1.1 200 OK +Content-Length: 50 +Foo: Bar`, + }, + + { + res: &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: 3, + Body: ioutil.NopCloser(strings.NewReader("foo")), + }, + body: true, + want: `HTTP/1.1 200 OK +Content-Length: 3 + +foo`, + }, + + { + res: &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: -1, + Body: ioutil.NopCloser(strings.NewReader("foo")), + TransferEncoding: []string{"chunked"}, + }, + body: true, + want: `HTTP/1.1 200 OK +Transfer-Encoding: chunked + +3 +foo +0`, + }, +} + +func TestDumpResponse(t *testing.T) { + for i, tt := range dumpResTests { + gotb, err := DumpResponse(tt.res, tt.body) + if err != nil { + t.Errorf("%d. DumpResponse = %v", i, err) + continue + } + got := string(gotb) + got = strings.TrimSpace(got) + got = strings.Replace(got, "\r", "", -1) + + if got != tt.want { + t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want) + } + } +} diff --git a/libgo/go/net/http/httputil/persist.go b/libgo/go/net/http/httputil/persist.go index 86d23e03706..987bcc96ba1 100644 --- a/libgo/go/net/http/httputil/persist.go +++ b/libgo/go/net/http/httputil/persist.go @@ -31,8 +31,8 @@ var errClosed = errors.New("i/o operation on closed connection") // i.e. requests can be read out of sync (but in the same order) while the // respective responses are sent. // -// ServerConn is low-level and should not be needed by most applications. -// See Server. +// ServerConn is low-level and old. Applications should instead use Server +// in the net/http package. type ServerConn struct { lk sync.Mutex // read-write protects the following fields c net.Conn @@ -45,8 +45,11 @@ type ServerConn struct { pipe textproto.Pipeline } -// NewServerConn returns a new ServerConn reading and writing c. If r is not +// NewServerConn returns a new ServerConn reading and writing c. If r is not // nil, it is the buffer to use when reading c. +// +// ServerConn is low-level and old. Applications should instead use Server +// in the net/http package. func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn { if r == nil { r = bufio.NewReader(c) @@ -221,8 +224,8 @@ func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error { // supports hijacking the connection calling Hijack to // regain control of the underlying net.Conn and deal with it as desired. // -// ClientConn is low-level and should not be needed by most applications. -// See Client. +// ClientConn is low-level and old. Applications should instead use +// Client or Transport in the net/http package. type ClientConn struct { lk sync.Mutex // read-write protects the following fields c net.Conn @@ -238,6 +241,9 @@ type ClientConn struct { // NewClientConn returns a new ClientConn reading and writing c. If r is not // nil, it is the buffer to use when reading c. +// +// ClientConn is low-level and old. Applications should use Client or +// Transport in the net/http package. func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { if r == nil { r = bufio.NewReader(c) @@ -252,6 +258,9 @@ func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { // NewProxyClientConn works like NewClientConn but writes Requests // using Request's WriteProxy method. +// +// New code should not use NewProxyClientConn. See Client or +// Transport in the net/http package instead. func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn { cc := NewClientConn(c, r) cc.writeReq = (*http.Request).WriteProxy diff --git a/libgo/go/net/http/proxy_test.go b/libgo/go/net/http/proxy_test.go index d0726f61f3b..b6aed3792b6 100644 --- a/libgo/go/net/http/proxy_test.go +++ b/libgo/go/net/http/proxy_test.go @@ -35,12 +35,8 @@ var UseProxyTests = []struct { } func TestUseProxy(t *testing.T) { - oldenv := os.Getenv("NO_PROXY") - defer os.Setenv("NO_PROXY", oldenv) - - no_proxy := "foobar.com, .barbaz.net" - os.Setenv("NO_PROXY", no_proxy) - + ResetProxyEnv() + os.Setenv("NO_PROXY", "foobar.com, .barbaz.net") for _, test := range UseProxyTests { if useProxy(test.host+":80") != test.match { t.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) @@ -76,3 +72,10 @@ func TestCacheKeys(t *testing.T) { } } } + +func ResetProxyEnv() { + for _, v := range []string{"HTTP_PROXY", "http_proxy", "NO_PROXY", "no_proxy"} { + os.Setenv(v, "") + } + ResetCachedEnvironment() +} diff --git a/libgo/go/net/http/race.go b/libgo/go/net/http/race.go new file mode 100644 index 00000000000..766503967c3 --- /dev/null +++ b/libgo/go/net/http/race.go @@ -0,0 +1,11 @@ +// Copyright 2014 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. + +// +build race + +package http + +func init() { + raceEnabled = true +} diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index 7a97770314d..a67092066ad 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -69,18 +69,31 @@ var reqWriteExcludeHeader = map[string]bool{ // A Request represents an HTTP request received by a server // or to be sent by a client. +// +// The field semantics differ slightly between client and server +// usage. In addition to the notes on the fields below, see the +// documentation for Request.Write and RoundTripper. type Request struct { - Method string // GET, POST, PUT, etc. + // Method specifies the HTTP method (GET, POST, PUT, etc.). + // For client requests an empty string means GET. + Method string - // URL is created from the URI supplied on the Request-Line - // as stored in RequestURI. + // URL specifies either the URI being requested (for server + // requests) or the URL to access (for client requests). + // + // For server requests the URL is parsed from the URI + // supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be + // empty. (See RFC 2616, Section 5.1.2) // - // For most requests, fields other than Path and RawQuery - // will be empty. (See RFC 2616, Section 5.1.2) + // For client requests, the URL's Host specifies the server to + // connect to, while the Request's Host field optionally + // specifies the Host header value to send in the HTTP + // request. URL *url.URL // The protocol version for incoming requests. - // Outgoing requests always use HTTP/1.1. + // Client requests always use HTTP/1.1. Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 @@ -104,15 +117,20 @@ type Request struct { // The request parser implements this by canonicalizing the // name, making the first character and any characters // following a hyphen uppercase and the rest lowercase. + // + // For client requests certain headers are automatically + // added and may override values in Header. + // + // See the documentation for the Request.Write method. Header Header // Body is the request's body. // - // For client requests, a nil body means the request has no + // For client requests a nil body means the request has no // body, such as a GET request. The HTTP Client's Transport // is responsible for calling the Close method. // - // For server requests, the Request Body is always non-nil + // For server requests the Request Body is always non-nil // but will return EOF immediately when no body is present. // The Server will close the request body. The ServeHTTP // Handler does not need to. @@ -122,7 +140,7 @@ type Request struct { // The value -1 indicates that the length is unknown. // Values >= 0 indicate that the given number of bytes may // be read from Body. - // For outgoing requests, a value of 0 means unknown if Body is not nil. + // For client requests, a value of 0 means unknown if Body is not nil. ContentLength int64 // TransferEncoding lists the transfer encodings from outermost to @@ -133,13 +151,18 @@ type Request struct { TransferEncoding []string // Close indicates whether to close the connection after - // replying to this request. + // replying to this request (for servers) or after sending + // the request (for clients). Close bool - // The host on which the URL is sought. - // Per RFC 2616, this is either the value of the Host: header - // or the host name given in the URL itself. + // For server requests Host specifies the host on which the + // URL is sought. Per RFC 2616, this is either the value of + // the "Host" header or the host name given in the URL itself. // It may be of the form "host:port". + // + // For client requests Host optionally overrides the Host + // header to send. If empty, the Request.Write method uses + // the value of URL.Host. Host string // Form contains the parsed form data, including both the URL @@ -159,12 +182,24 @@ type Request struct { // The HTTP client ignores MultipartForm and uses Body instead. MultipartForm *multipart.Form - // Trailer maps trailer keys to values. Like for Header, if the - // response has multiple trailer lines with the same key, they will be - // concatenated, delimited by commas. - // For server requests, Trailer is only populated after Body has been - // closed or fully consumed. - // Trailer support is only partially complete. + // Trailer specifies additional headers that are sent after the request + // body. + // + // For server requests the Trailer map initially contains only the + // trailer keys, with nil values. (The client declares which trailers it + // will later send.) While the handler is reading from Body, it must + // not reference Trailer. After reading from Body returns EOF, Trailer + // can be read again and will contain non-nil values, if they were sent + // by the client. + // + // For client requests Trailer must be initialized to a map containing + // the trailer keys to later send. The values may be nil or their final + // values. The ContentLength must be 0 or -1, to send a chunked request. + // After the HTTP request is sent the map values can be updated while + // the request body is read. Once the body returns EOF, the caller must + // not mutate Trailer. + // + // Few HTTP clients, servers, or proxies support HTTP trailers. Trailer Header // RemoteAddr allows HTTP servers and other software to record @@ -382,7 +417,6 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err return err } - // TODO: split long values? (If so, should share code with Conn.Write) err = req.Header.WriteSubset(w, reqWriteExcludeHeader) if err != nil { return err @@ -584,32 +618,6 @@ func ReadRequest(b *bufio.Reader) (req *Request, err error) { fixPragmaCacheControl(req.Header) - // TODO: Parse specific header values: - // Accept - // Accept-Encoding - // Accept-Language - // Authorization - // Cache-Control - // Connection - // Date - // Expect - // From - // If-Match - // If-Modified-Since - // If-None-Match - // If-Range - // If-Unmodified-Since - // Max-Forwards - // Proxy-Authorization - // Referer [sic] - // TE (transfer-codings) - // Trailer - // Transfer-Encoding - // Upgrade - // User-Agent - // Via - // Warning - err = readTransfer(req, b) if err != nil { return nil, err @@ -728,7 +736,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) { func (r *Request) ParseForm() error { var err error if r.PostForm == nil { - if r.Method == "POST" || r.Method == "PUT" { + if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" { r.PostForm, err = parsePostForm(r) } if r.PostForm == nil { @@ -781,9 +789,7 @@ func (r *Request) ParseMultipartForm(maxMemory int64) error { } mr, err := r.multipartReader() - if err == ErrNotMultipart { - return nil - } else if err != nil { + if err != nil { return err } @@ -861,3 +867,9 @@ func (r *Request) wantsHttp10KeepAlive() bool { func (r *Request) wantsClose() bool { return hasToken(r.Header.get("Connection"), "close") } + +func (r *Request) closeBody() { + if r.Body != nil { + r.Body.Close() + } +} diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index 68d141398aa..b9fa3c2bfc4 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -60,6 +60,37 @@ func TestPostQuery(t *testing.T) { } } +func TestPatchQuery(t *testing.T) { + req, _ := NewRequest("PATCH", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&empty=not", + strings.NewReader("z=post&both=y&prio=2&empty=")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") + + if q := req.FormValue("q"); q != "foo" { + t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) + } + if z := req.FormValue("z"); z != "post" { + t.Errorf(`req.FormValue("z") = %q, want "post"`, z) + } + if bq, found := req.PostForm["q"]; found { + t.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq) + } + if bz := req.PostFormValue("z"); bz != "post" { + t.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz) + } + if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) { + t.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs) + } + if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) { + t.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both) + } + if prio := req.FormValue("prio"); prio != "2" { + t.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio) + } + if empty := req.FormValue("empty"); empty != "" { + t.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty) + } +} + type stringMap map[string][]string type parseContentTypeTest struct { shouldError bool @@ -123,7 +154,25 @@ func TestMultipartReader(t *testing.T) { req.Header = Header{"Content-Type": {"text/plain"}} multipart, err = req.MultipartReader() if multipart != nil { - t.Errorf("unexpected multipart for text/plain") + t.Error("unexpected multipart for text/plain") + } +} + +func TestParseMultipartForm(t *testing.T) { + req := &Request{ + Method: "POST", + Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, + Body: ioutil.NopCloser(new(bytes.Buffer)), + } + err := req.ParseMultipartForm(25) + if err == nil { + t.Error("expected multipart EOF, got nil") + } + + req.Header = Header{"Content-Type": {"text/plain"}} + err = req.ParseMultipartForm(25) + if err != ErrNotMultipart { + t.Error("expected ErrNotMultipart for text/plain") } } @@ -189,16 +238,38 @@ func TestMultipartRequestAuto(t *testing.T) { validateTestMultipartContents(t, req, true) } -func TestEmptyMultipartRequest(t *testing.T) { - // Test that FormValue and FormFile automatically invoke - // ParseMultipartForm and return the right values. - req, err := NewRequest("GET", "/", nil) - if err != nil { - t.Errorf("NewRequest err = %q", err) - } +func TestMissingFileMultipartRequest(t *testing.T) { + // Test that FormFile returns an error if + // the named file is missing. + req := newTestMultipartRequest(t) testMissingFile(t, req) } +// Test that FormValue invokes ParseMultipartForm. +func TestFormValueCallsParseMultipartForm(t *testing.T) { + req, _ := NewRequest("POST", "http://www.google.com/", strings.NewReader("z=post")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") + if req.Form != nil { + t.Fatal("Unexpected request Form, want nil") + } + req.FormValue("z") + if req.Form == nil { + t.Fatal("ParseMultipartForm not called by FormValue") + } +} + +// Test that FormFile invokes ParseMultipartForm. +func TestFormFileCallsParseMultipartForm(t *testing.T) { + req := newTestMultipartRequest(t) + if req.Form != nil { + t.Fatal("Unexpected request Form, want nil") + } + req.FormFile("") + if req.Form == nil { + t.Fatal("ParseMultipartForm not called by FormFile") + } +} + // Test that ParseMultipartForm errors if called // after MultipartReader on the same request. func TestParseMultipartFormOrder(t *testing.T) { diff --git a/libgo/go/net/http/requestwrite_test.go b/libgo/go/net/http/requestwrite_test.go index 561eea28e5a..dc0e204cac9 100644 --- a/libgo/go/net/http/requestwrite_test.go +++ b/libgo/go/net/http/requestwrite_test.go @@ -310,6 +310,46 @@ var reqWriteTests = []reqWriteTest{ WantError: errors.New("http: Request.ContentLength=5 with nil Body"), }, + // Request with a 0 ContentLength and a body with 1 byte content and an error. + { + Req: Request{ + Method: "POST", + URL: mustParseURL("/"), + Host: "example.com", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: 0, // as if unset by user + }, + + Body: func() io.ReadCloser { + err := errors.New("Custom reader error") + errReader := &errorReader{err} + return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader)) + }, + + WantError: errors.New("Custom reader error"), + }, + + // Request with a 0 ContentLength and a body without content and an error. + { + Req: Request{ + Method: "POST", + URL: mustParseURL("/"), + Host: "example.com", + ProtoMajor: 1, + ProtoMinor: 1, + ContentLength: 0, // as if unset by user + }, + + Body: func() io.ReadCloser { + err := errors.New("Custom reader error") + errReader := &errorReader{err} + return ioutil.NopCloser(errReader) + }, + + WantError: errors.New("Custom reader error"), + }, + // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, // and doesn't add a User-Agent. { diff --git a/libgo/go/net/http/response.go b/libgo/go/net/http/response.go index 0b991c72ef0..5d2c39080e4 100644 --- a/libgo/go/net/http/response.go +++ b/libgo/go/net/http/response.go @@ -8,6 +8,8 @@ package http import ( "bufio" + "bytes" + "crypto/tls" "errors" "io" "net/textproto" @@ -45,7 +47,8 @@ type Response struct { // // The http Client and Transport guarantee that Body is always // non-nil, even on responses without a body or responses with - // a zero-lengthed body. + // a zero-length body. It is the caller's responsibility to + // close Body. // // The Body is automatically dechunked if the server replied // with a "chunked" Transfer-Encoding. @@ -74,6 +77,12 @@ type Response struct { // Request's Body is nil (having already been consumed). // This is only populated for Client requests. Request *Request + + // TLS contains information about the TLS connection on which the + // response was received. It is nil for unencrypted responses. + // The pointer is shared between responses and should not be + // modified. + TLS *tls.ConnectionState } // Cookies parses and returns the cookies set in the Set-Cookie headers. @@ -192,7 +201,6 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // // Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { - // Status line text := r.Status if text == "" { @@ -205,10 +213,45 @@ func (r *Response) Write(w io.Writer) error { protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor) statusCode := strconv.Itoa(r.StatusCode) + " " text = strings.TrimPrefix(text, statusCode) - io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n") + if _, err := io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n"); err != nil { + return err + } + + // Clone it, so we can modify r1 as needed. + r1 := new(Response) + *r1 = *r + if r1.ContentLength == 0 && r1.Body != nil { + // Is it actually 0 length? Or just unknown? + var buf [1]byte + n, err := r1.Body.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + if n == 0 { + // Reset it to a known zero reader, in case underlying one + // is unhappy being read repeatedly. + r1.Body = eofReader + } else { + r1.ContentLength = -1 + r1.Body = struct { + io.Reader + io.Closer + }{ + io.MultiReader(bytes.NewReader(buf[:1]), r.Body), + r.Body, + } + } + } + // If we're sending a non-chunked HTTP/1.1 response without a + // content-length, the only way to do that is the old HTTP/1.0 + // way, by noting the EOF with a connection close, so we need + // to set Close. + if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) { + r1.Close = true + } // Process Body,ContentLength,Close,Trailer - tw, err := newTransferWriter(r) + tw, err := newTransferWriter(r1) if err != nil { return err } @@ -223,8 +266,19 @@ func (r *Response) Write(w io.Writer) error { return err } + // contentLengthAlreadySent may have been already sent for + // POST/PUT requests, even if zero length. See Issue 8180. + contentLengthAlreadySent := tw.shouldSendContentLength() + if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent { + if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil { + return err + } + } + // End-of-header - io.WriteString(w, "\r\n") + if _, err := io.WriteString(w, "\r\n"); err != nil { + return err + } // Write body and trailer err = tw.WriteBody(w) diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go index d6e77b19c10..4b8946f7ae4 100644 --- a/libgo/go/net/http/response_test.go +++ b/libgo/go/net/http/response_test.go @@ -29,6 +29,10 @@ func dummyReq(method string) *Request { return &Request{Method: method} } +func dummyReq11(method string) *Request { + return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} +} + var respTests = []respTest{ // Unchunked response without Content-Length. { diff --git a/libgo/go/net/http/responsewrite_test.go b/libgo/go/net/http/responsewrite_test.go index 4799b4792b3..585b13b8504 100644 --- a/libgo/go/net/http/responsewrite_test.go +++ b/libgo/go/net/http/responsewrite_test.go @@ -26,7 +26,7 @@ func TestResponseWrite(t *testing.T) { ProtoMinor: 0, Request: dummyReq("GET"), Header: Header{}, - Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), + Body: ioutil.NopCloser(strings.NewReader("abcdef")), ContentLength: 6, }, @@ -49,6 +49,106 @@ func TestResponseWrite(t *testing.T) { "\r\n" + "abcdef", }, + // HTTP/1.1 response with unknown length and Connection: close + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq("GET"), + Header: Header{}, + Body: ioutil.NopCloser(strings.NewReader("abcdef")), + ContentLength: -1, + Close: true, + }, + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "abcdef", + }, + // HTTP/1.1 response with unknown length and not setting connection: close + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq11("GET"), + Header: Header{}, + Body: ioutil.NopCloser(strings.NewReader("abcdef")), + ContentLength: -1, + Close: false, + }, + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "\r\n" + + "abcdef", + }, + // HTTP/1.1 response with unknown length and not setting connection: close, but + // setting chunked. + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq11("GET"), + Header: Header{}, + Body: ioutil.NopCloser(strings.NewReader("abcdef")), + ContentLength: -1, + TransferEncoding: []string{"chunked"}, + Close: false, + }, + "HTTP/1.1 200 OK\r\n" + + "Transfer-Encoding: chunked\r\n\r\n" + + "6\r\nabcdef\r\n0\r\n\r\n", + }, + // HTTP/1.1 response 0 content-length, and nil body + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq11("GET"), + Header: Header{}, + Body: nil, + ContentLength: 0, + Close: false, + }, + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 0\r\n" + + "\r\n", + }, + // HTTP/1.1 response 0 content-length, and non-nil empty body + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq11("GET"), + Header: Header{}, + Body: ioutil.NopCloser(strings.NewReader("")), + ContentLength: 0, + Close: false, + }, + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 0\r\n" + + "\r\n", + }, + // HTTP/1.1 response 0 content-length, and non-nil non-empty body + { + Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq11("GET"), + Header: Header{}, + Body: ioutil.NopCloser(strings.NewReader("foo")), + ContentLength: 0, + Close: false, + }, + "HTTP/1.1 200 OK\r\n" + + "Connection: close\r\n" + + "\r\nfoo", + }, // HTTP/1.1, chunked coding; empty trailer; close { Response{ @@ -91,6 +191,22 @@ func TestResponseWrite(t *testing.T) { "Foo: Bar Baz\r\n" + "\r\n", }, + + // Want a single Content-Length header. Fixing issue 8180 where + // there were two. + { + Response{ + StatusCode: StatusOK, + ProtoMajor: 1, + ProtoMinor: 1, + Request: &Request{Method: "POST"}, + Header: Header{}, + ContentLength: 0, + TransferEncoding: nil, + Body: nil, + }, + "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", + }, } for i := range respWriteTests { diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index e7a3e6ea75f..8371dd82f58 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -851,7 +851,9 @@ func TestTLSHandshakeTimeout(t *testing.T) { } defer afterTest(t) ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) + errc := make(chanWriter, 10) // but only expecting 1 ts.Config.ReadTimeout = 250 * time.Millisecond + ts.Config.ErrorLog = log.New(errc, "", 0) ts.StartTLS() defer ts.Close() conn, err := net.Dial("tcp", ts.Listener.Addr().String()) @@ -866,6 +868,14 @@ func TestTLSHandshakeTimeout(t *testing.T) { t.Errorf("Read = %d, %v; want an error and no bytes", n, err) } }) + select { + case v := <-errc: + if !strings.Contains(v, "timeout") && !strings.Contains(v, "TLS handshake") { + t.Errorf("expected a TLS handshake timeout error; got %q", v) + } + case <-time.After(5 * time.Second): + t.Errorf("timeout waiting for logged error") + } } func TestTLSServer(t *testing.T) { @@ -878,6 +888,7 @@ func TestTLSServer(t *testing.T) { } } })) + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) defer ts.Close() // Connect an idle TCP connection to this server before we run @@ -922,31 +933,50 @@ func TestTLSServer(t *testing.T) { } type serverExpectTest struct { - contentLength int // of request body + contentLength int // of request body + chunked bool expectation string // e.g. "100-continue" readBody bool // whether handler should read the body (if false, sends StatusUnauthorized) expectedResponse string // expected substring in first line of http response } +func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest { + return serverExpectTest{ + contentLength: contentLength, + expectation: expectation, + readBody: readBody, + expectedResponse: expectedResponse, + } +} + var serverExpectTests = []serverExpectTest{ // Normal 100-continues, case-insensitive. - {100, "100-continue", true, "100 Continue"}, - {100, "100-cOntInUE", true, "100 Continue"}, + expectTest(100, "100-continue", true, "100 Continue"), + expectTest(100, "100-cOntInUE", true, "100 Continue"), // No 100-continue. - {100, "", true, "200 OK"}, + expectTest(100, "", true, "200 OK"), // 100-continue but requesting client to deny us, // so it never reads the body. - {100, "100-continue", false, "401 Unauthorized"}, + expectTest(100, "100-continue", false, "401 Unauthorized"), // Likewise without 100-continue: - {100, "", false, "401 Unauthorized"}, + expectTest(100, "", false, "401 Unauthorized"), // Non-standard expectations are failures - {0, "a-pony", false, "417 Expectation Failed"}, + expectTest(0, "a-pony", false, "417 Expectation Failed"), - // Expect-100 requested but no body - {0, "100-continue", true, "400 Bad Request"}, + // Expect-100 requested but no body (is apparently okay: Issue 7625) + expectTest(0, "100-continue", true, "200 OK"), + // Expect-100 requested but handler doesn't read the body + expectTest(0, "100-continue", false, "401 Unauthorized"), + // Expect-100 continue with no body, but a chunked body. + { + expectation: "100-continue", + readBody: true, + chunked: true, + expectedResponse: "100 Continue", + }, } // Tests that the server responds to the "Expect" request header @@ -975,21 +1005,38 @@ func TestServerExpect(t *testing.T) { // Only send the body immediately if we're acting like an HTTP client // that doesn't send 100-continue expectations. - writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue" + writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue" go func() { + contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength) + if test.chunked { + contentLen = "Transfer-Encoding: chunked" + } _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+ "Connection: close\r\n"+ - "Content-Length: %d\r\n"+ + "%s\r\n"+ "Expect: %s\r\nHost: foo\r\n\r\n", - test.readBody, test.contentLength, test.expectation) + test.readBody, contentLen, test.expectation) if err != nil { t.Errorf("On test %#v, error writing request headers: %v", test, err) return } if writeBody { + var targ io.WriteCloser = struct { + io.Writer + io.Closer + }{ + conn, + ioutil.NopCloser(nil), + } + if test.chunked { + targ = httputil.NewChunkedWriter(conn) + } body := strings.Repeat("A", test.contentLength) - _, err = fmt.Fprint(conn, body) + _, err = fmt.Fprint(targ, body) + if err == nil { + err = targ.Close() + } if err != nil { if !test.readBody { // Server likely already hung up on us. @@ -2097,7 +2144,7 @@ func TestCodesPreventingContentTypeAndBody(t *testing.T) { got := ht.rawResponse(req) wantStatus := fmt.Sprintf("%d %s", code, StatusText(code)) if !strings.Contains(got, wantStatus) { - t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got) + t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got) } else if strings.Contains(got, "Content-Length") { t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got) } else if strings.Contains(got, "stuff") { @@ -2107,6 +2154,21 @@ func TestCodesPreventingContentTypeAndBody(t *testing.T) { } } +func TestContentTypeOkayOn204(t *testing.T) { + ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Content-Length", "123") // suppressed + w.Header().Set("Content-Type", "foo/bar") + w.WriteHeader(204) + })) + got := ht.rawResponse("GET / HTTP/1.1") + if !strings.Contains(got, "Content-Type: foo/bar") { + t.Errorf("Response = %q; want Content-Type: foo/bar", got) + } + if strings.Contains(got, "Content-Length: 123") { + t.Errorf("Response = %q; don't want a Content-Length", got) + } +} + // Issue 6995 // A server Handler can receive a Request, and then turn around and // give a copy of that Request.Body out to the Transport (e.g. any @@ -2225,8 +2287,8 @@ func TestResponseWriterWriteStringAllocs(t *testing.T) { w.Write([]byte("Hello world")) } })) - before := testing.AllocsPerRun(25, func() { ht.rawResponse("GET / HTTP/1.0") }) - after := testing.AllocsPerRun(25, func() { ht.rawResponse("GET /s HTTP/1.0") }) + before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") }) + after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") }) if int(after) >= int(before) { t.Errorf("WriteString allocs of %v >= Write allocs of %v", after, before) } @@ -2245,6 +2307,230 @@ func TestAppendTime(t *testing.T) { } } +func TestServerConnState(t *testing.T) { + defer afterTest(t) + handler := map[string]func(w ResponseWriter, r *Request){ + "/": func(w ResponseWriter, r *Request) { + fmt.Fprintf(w, "Hello.") + }, + "/close": func(w ResponseWriter, r *Request) { + w.Header().Set("Connection", "close") + fmt.Fprintf(w, "Hello.") + }, + "/hijack": func(w ResponseWriter, r *Request) { + c, _, _ := w.(Hijacker).Hijack() + c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) + c.Close() + }, + "/hijack-panic": func(w ResponseWriter, r *Request) { + c, _, _ := w.(Hijacker).Hijack() + c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) + c.Close() + panic("intentional panic") + }, + } + ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { + handler[r.URL.Path](w, r) + })) + defer ts.Close() + + var mu sync.Mutex // guard stateLog and connID + var stateLog = map[int][]ConnState{} + var connID = map[net.Conn]int{} + + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) + ts.Config.ConnState = func(c net.Conn, state ConnState) { + if c == nil { + t.Errorf("nil conn seen in state %s", state) + return + } + mu.Lock() + defer mu.Unlock() + id, ok := connID[c] + if !ok { + id = len(connID) + 1 + connID[c] = id + } + stateLog[id] = append(stateLog[id], state) + } + ts.Start() + + mustGet(t, ts.URL+"/") + mustGet(t, ts.URL+"/close") + + mustGet(t, ts.URL+"/") + mustGet(t, ts.URL+"/", "Connection", "close") + + mustGet(t, ts.URL+"/hijack") + mustGet(t, ts.URL+"/hijack-panic") + + // New->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + c.Close() + } + + // New->Active->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + if _, err := io.WriteString(c, "BOGUS REQUEST\r\n\r\n"); err != nil { + t.Fatal(err) + } + c.Close() + } + + // New->Idle->Closed + { + c, err := net.Dial("tcp", ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + if _, err := io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"); err != nil { + t.Fatal(err) + } + res, err := ReadResponse(bufio.NewReader(c), nil) + if err != nil { + t.Fatal(err) + } + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + t.Fatal(err) + } + c.Close() + } + + want := map[int][]ConnState{ + 1: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 2: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, + 3: []ConnState{StateNew, StateActive, StateHijacked}, + 4: []ConnState{StateNew, StateActive, StateHijacked}, + 5: []ConnState{StateNew, StateClosed}, + 6: []ConnState{StateNew, StateActive, StateClosed}, + 7: []ConnState{StateNew, StateActive, StateIdle, StateClosed}, + } + logString := func(m map[int][]ConnState) string { + var b bytes.Buffer + for id, l := range m { + fmt.Fprintf(&b, "Conn %d: ", id) + for _, s := range l { + fmt.Fprintf(&b, "%s ", s) + } + b.WriteString("\n") + } + return b.String() + } + + for i := 0; i < 5; i++ { + time.Sleep(time.Duration(i) * 50 * time.Millisecond) + mu.Lock() + match := reflect.DeepEqual(stateLog, want) + mu.Unlock() + if match { + return + } + } + + mu.Lock() + t.Errorf("Unexpected events.\nGot log: %s\n Want: %s\n", logString(stateLog), logString(want)) + mu.Unlock() +} + +func mustGet(t *testing.T, url string, headers ...string) { + req, err := NewRequest("GET", url, nil) + if err != nil { + t.Fatal(err) + } + for len(headers) > 0 { + req.Header.Add(headers[0], headers[1]) + headers = headers[2:] + } + res, err := DefaultClient.Do(req) + if err != nil { + t.Errorf("Error fetching %s: %v", url, err) + return + } + _, err = ioutil.ReadAll(res.Body) + defer res.Body.Close() + if err != nil { + t.Errorf("Error reading %s: %v", url, err) + } +} + +func TestServerKeepAlivesEnabled(t *testing.T) { + defer afterTest(t) + ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) + ts.Config.SetKeepAlivesEnabled(false) + ts.Start() + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + if !res.Close { + t.Errorf("Body.Close == false; want true") + } +} + +// golang.org/issue/7856 +func TestServerEmptyBodyRace(t *testing.T) { + defer afterTest(t) + var n int32 + ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + atomic.AddInt32(&n, 1) + })) + defer ts.Close() + var wg sync.WaitGroup + const reqs = 20 + for i := 0; i < reqs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + res, err := Get(ts.URL) + if err != nil { + t.Error(err) + return + } + defer res.Body.Close() + _, err = io.Copy(ioutil.Discard, res.Body) + if err != nil { + t.Error(err) + return + } + }() + } + wg.Wait() + if got := atomic.LoadInt32(&n); got != reqs { + t.Errorf("handler ran %d times; want %d", got, reqs) + } +} + +func TestServerConnStateNew(t *testing.T) { + sawNew := false // if the test is buggy, we'll race on this variable. + srv := &Server{ + ConnState: func(c net.Conn, state ConnState) { + if state == StateNew { + sawNew = true // testing that this write isn't racy + } + }, + Handler: HandlerFunc(func(w ResponseWriter, r *Request) {}), // irrelevant + } + srv.Serve(&oneConnListener{ + conn: &rwTestConn{ + Reader: strings.NewReader("GET / HTTP/1.1\r\nHost: foo\r\n\r\n"), + Writer: ioutil.Discard, + }, + }) + if !sawNew { // testing that this read isn't racy + t.Error("StateNew not seen") + } +} + func BenchmarkClientServer(b *testing.B) { b.ReportAllocs() b.StopTimer() @@ -2259,7 +2545,6 @@ func BenchmarkClientServer(b *testing.B) { if err != nil { b.Fatal("Get:", err) } - defer res.Body.Close() all, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { @@ -2282,42 +2567,33 @@ func BenchmarkClientServerParallel64(b *testing.B) { benchmarkClientServerParallel(b, 64) } -func benchmarkClientServerParallel(b *testing.B, conc int) { +func benchmarkClientServerParallel(b *testing.B, parallelism int) { b.ReportAllocs() - b.StopTimer() ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { fmt.Fprintf(rw, "Hello world.\n") })) defer ts.Close() - b.StartTimer() - - numProcs := runtime.GOMAXPROCS(-1) * conc - var wg sync.WaitGroup - wg.Add(numProcs) - n := int32(b.N) - for p := 0; p < numProcs; p++ { - go func() { - for atomic.AddInt32(&n, -1) >= 0 { - res, err := Get(ts.URL) - if err != nil { - b.Logf("Get: %v", err) - continue - } - all, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - b.Logf("ReadAll: %v", err) - continue - } - body := string(all) - if body != "Hello world.\n" { - panic("Got body: " + body) - } + b.ResetTimer() + b.SetParallelism(parallelism) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + res, err := Get(ts.URL) + if err != nil { + b.Logf("Get: %v", err) + continue } - wg.Done() - }() - } - wg.Wait() + all, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + b.Logf("ReadAll: %v", err) + continue + } + body := string(all) + if body != "Hello world.\n" { + panic("Got body: " + body) + } + } + }) } // A benchmark for profiling the server without the HTTP client code. diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index fea1898fd7e..eae097eb8e9 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" ) @@ -138,6 +139,7 @@ func (c *conn) hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { buf = c.buf c.rwc = nil c.buf = nil + c.setState(rwc, StateHijacked) return } @@ -496,6 +498,10 @@ func (srv *Server) maxHeaderBytes() int { return DefaultMaxHeaderBytes } +func (srv *Server) initialLimitedReaderSize() int64 { + return int64(srv.maxHeaderBytes()) + 4096 // bufio slop +} + // wrapper around io.ReaderCloser which on first read, sends an // HTTP/1.1 100 Continue header type expectContinueReader struct { @@ -566,7 +572,7 @@ func (c *conn) readRequest() (w *response, err error) { }() } - c.lr.N = int64(c.server.maxHeaderBytes()) + 4096 /* bufio slop */ + c.lr.N = c.server.initialLimitedReaderSize() var req *Request if req, err = ReadRequest(c.buf.Reader); err != nil { if c.lr.N == 0 { @@ -614,11 +620,11 @@ const maxPostHandlerReadBytes = 256 << 10 func (w *response) WriteHeader(code int) { if w.conn.hijacked() { - log.Print("http: response.WriteHeader on hijacked connection") + w.conn.server.logf("http: response.WriteHeader on hijacked connection") return } if w.wroteHeader { - log.Print("http: multiple response.WriteHeader calls") + w.conn.server.logf("http: multiple response.WriteHeader calls") return } w.wroteHeader = true @@ -633,7 +639,7 @@ func (w *response) WriteHeader(code int) { if err == nil && v >= 0 { w.contentLength = v } else { - log.Printf("http: invalid Content-Length of %q", cl) + w.conn.server.logf("http: invalid Content-Length of %q", cl) w.handlerHeader.Del("Content-Length") } } @@ -703,6 +709,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { cw.wroteHeader = true w := cw.res + keepAlivesEnabled := w.conn.server.doKeepAlives() isHEAD := w.req.Method == "HEAD" // header is written out to w.conn.buf below. Depending on the @@ -750,7 +757,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { // If this was an HTTP/1.0 request with keep-alive and we sent a // Content-Length back, we can make this a keep-alive response ... - if w.req.wantsHttp10KeepAlive() { + if w.req.wantsHttp10KeepAlive() && keepAlivesEnabled { sentLength := header.get("Content-Length") != "" if sentLength && header.get("Connection") == "keep-alive" { w.closeAfterReply = false @@ -769,7 +776,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { w.closeAfterReply = true } - if header.get("Connection") == "close" { + if header.get("Connection") == "close" || !keepAlivesEnabled { w.closeAfterReply = true } @@ -792,18 +799,16 @@ func (cw *chunkWriter) writeHeader(p []byte) { } code := w.status - if !bodyAllowedForStatus(code) { - // Must not have body. - // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" - for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} { - delHeader(k) - } - } else { + if bodyAllowedForStatus(code) { // If no content type, apply sniffing algorithm to body. _, haveType := header["Content-Type"] if !haveType { setHeader.contentType = DetectContentType(p) } + } else { + for _, k := range suppressedHeaders(code) { + delHeader(k) + } } if _, ok := header["Date"]; !ok { @@ -815,7 +820,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { if hasCL && hasTE && te != "identity" { // TODO: return an error if WriteHeader gets a return parameter // For now just ignore the Content-Length. - log.Printf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d", + w.conn.server.logf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d", te, w.contentLength) delHeader("Content-Length") hasCL = false @@ -851,7 +856,7 @@ func (cw *chunkWriter) writeHeader(p []byte) { return } - if w.closeAfterReply && !hasToken(cw.header.get("Connection"), "close") { + if w.closeAfterReply && (!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) { delHeader("Connection") if w.req.ProtoAtLeast(1, 1) { setHeader.connection = "close" @@ -961,7 +966,7 @@ func (w *response) WriteString(data string) (n int, err error) { // either dataB or dataS is non-zero. func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) { if w.conn.hijacked() { - log.Print("http: response.Write on hijacked connection") + w.conn.server.logf("http: response.Write on hijacked connection") return 0, ErrHijacked } if !w.wroteHeader { @@ -1079,17 +1084,25 @@ func validNPN(proto string) bool { return true } +func (c *conn) setState(nc net.Conn, state ConnState) { + if hook := c.server.ConnState; hook != nil { + hook(nc, state) + } +} + // Serve a new connection. func (c *conn) serve() { + origConn := c.rwc // copy it before it's set nil on Close or Hijack defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - log.Printf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) + c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() + c.setState(origConn, StateClosed) } }() @@ -1101,6 +1114,7 @@ func (c *conn) serve() { c.rwc.SetWriteDeadline(time.Now().Add(d)) } if err := tlsConn.Handshake(); err != nil { + c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) return } c.tlsState = new(tls.ConnectionState) @@ -1116,6 +1130,10 @@ func (c *conn) serve() { for { w, err := c.readRequest() + if c.lr.N != c.server.initialLimitedReaderSize() { + // If we read any bytes off the wire, we're active. + c.setState(c.rwc, StateActive) + } if err != nil { if err == errTooLarge { // Their HTTP client may or may not be @@ -1138,16 +1156,10 @@ func (c *conn) serve() { // Expect 100 Continue support req := w.req if req.expectsContinue() { - if req.ProtoAtLeast(1, 1) { + if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 { // Wrap the Body reader with one that replies on the connection req.Body = &expectContinueReader{readCloser: req.Body, resp: w} } - if req.ContentLength == 0 { - w.Header().Set("Connection", "close") - w.WriteHeader(StatusBadRequest) - w.finishRequest() - break - } req.Header.Del("Expect") } else if req.Header.get("Expect") != "" { w.sendExpectationFailed() @@ -1170,6 +1182,7 @@ func (c *conn) serve() { } break } + c.setState(c.rwc, StateIdle) } } @@ -1564,6 +1577,7 @@ func Serve(l net.Listener, handler Handler) error { } // A Server defines parameters for running an HTTP server. +// The zero value for Server is a valid configuration. type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil @@ -1580,6 +1594,66 @@ type Server struct { // and RemoteAddr if not already set. The connection is // automatically closed when the function returns. TLSNextProto map[string]func(*Server, *tls.Conn, Handler) + + // ConnState specifies an optional callback function that is + // called when a client connection changes state. See the + // ConnState type and associated constants for details. + ConnState func(net.Conn, ConnState) + + // ErrorLog specifies an optional logger for errors accepting + // connections and unexpected behavior from handlers. + // If nil, logging goes to os.Stderr via the log package's + // standard logger. + ErrorLog *log.Logger + + disableKeepAlives int32 // accessed atomically. +} + +// A ConnState represents the state of a client connection to a server. +// It's used by the optional Server.ConnState hook. +type ConnState int + +const ( + // StateNew represents a new connection that is expected to + // send a request immediately. Connections begin at this + // state and then transition to either StateActive or + // StateClosed. + StateNew ConnState = iota + + // StateActive represents a connection that has read 1 or more + // bytes of a request. The Server.ConnState hook for + // StateActive fires before the request has entered a handler + // and doesn't fire again until the request has been + // handled. After the request is handled, the state + // transitions to StateClosed, StateHijacked, or StateIdle. + StateActive + + // StateIdle represents a connection that has finished + // handling a request and is in the keep-alive state, waiting + // for a new request. Connections transition from StateIdle + // to either StateActive or StateClosed. + StateIdle + + // StateHijacked represents a hijacked connection. + // This is a terminal state. It does not transition to StateClosed. + StateHijacked + + // StateClosed represents a closed connection. + // This is a terminal state. Hijacked connections do not + // transition to StateClosed. + StateClosed +) + +var stateName = map[ConnState]string{ + StateNew: "new", + StateActive: "active", + StateIdle: "idle", + StateHijacked: "hijacked", + StateClosed: "closed", +} + +func (c ConnState) String() string { + return stateName[c] } // serverHandler delegates to either the server's Handler or @@ -1632,7 +1706,7 @@ func (srv *Server) Serve(l net.Listener) error { if max := 1 * time.Second; tempDelay > max { tempDelay = max } - log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) + srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } @@ -1643,10 +1717,35 @@ func (srv *Server) Serve(l net.Listener) error { if err != nil { continue } + c.setState(c.rwc, StateNew) // before Serve can return go c.serve() } } +func (s *Server) doKeepAlives() bool { + return atomic.LoadInt32(&s.disableKeepAlives) == 0 +} + +// SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled. +// By default, keep-alives are always enabled. Only very +// resource-constrained environments or servers in the process of +// shutting down should disable them. +func (s *Server) SetKeepAlivesEnabled(v bool) { + if v { + atomic.StoreInt32(&s.disableKeepAlives, 0) + } else { + atomic.StoreInt32(&s.disableKeepAlives, 1) + } +} + +func (s *Server) logf(format string, args ...interface{}) { + if s.ErrorLog != nil { + s.ErrorLog.Printf(format, args...) + } else { + log.Printf(format, args...) + } +} + // ListenAndServe listens on the TCP network address addr // and then calls Serve with handler to handle requests // on incoming connections. Handler is typically nil, @@ -1870,17 +1969,24 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) { } } +type eofReaderWithWriteTo struct{} + +func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil } +func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF } + // eofReader is a non-nil io.ReadCloser that always returns EOF. -// It embeds a *strings.Reader so it still has a WriteTo method -// and io.Copy won't need a buffer. +// It has a WriteTo method so io.Copy won't need a buffer. var eofReader = &struct { - *strings.Reader + eofReaderWithWriteTo io.Closer }{ - strings.NewReader(""), + eofReaderWithWriteTo{}, ioutil.NopCloser(nil), } +// Verify that an io.Copy from an eofReader won't require a buffer. +var _ io.WriterTo = eofReader + // initNPNRequest is an HTTP handler that initializes certain // uninitialized fields in its *Request. Such partially-initialized // Requests come from NPN protocol handlers. diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index 2eec9d9abc4..7f63686528a 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -12,11 +12,20 @@ import ( "io" "io/ioutil" "net/textproto" + "sort" "strconv" "strings" "sync" ) +type errorReader struct { + err error +} + +func (r *errorReader) Read(p []byte) (n int, err error) { + return 0, r.err +} + // transferWriter inspects the fields of a user-supplied Request or Response, // sanitizes them without changing the user object and provides methods for // writing the respective header, body and trailer in wire format. @@ -53,8 +62,11 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) { if t.ContentLength == 0 { // Test to see if it's actually zero or just unset. var buf [1]byte - n, _ := io.ReadFull(t.Body, buf[:]) - if n == 1 { + n, rerr := io.ReadFull(t.Body, buf[:]) + if rerr != nil && rerr != io.EOF { + t.ContentLength = -1 + t.Body = &errorReader{rerr} + } else if n == 1 { // Oh, guess there is data in this Body Reader after all. // The ContentLength field just wasn't set. // Stich the Body back together again, re-attaching our @@ -132,11 +144,10 @@ func (t *transferWriter) shouldSendContentLength() bool { return false } -func (t *transferWriter) WriteHeader(w io.Writer) (err error) { +func (t *transferWriter) WriteHeader(w io.Writer) error { if t.Close { - _, err = io.WriteString(w, "Connection: close\r\n") - if err != nil { - return + if _, err := io.WriteString(w, "Connection: close\r\n"); err != nil { + return err } } @@ -144,43 +155,44 @@ func (t *transferWriter) WriteHeader(w io.Writer) (err error) { // function of the sanitized field triple (Body, ContentLength, // TransferEncoding) if t.shouldSendContentLength() { - io.WriteString(w, "Content-Length: ") - _, err = io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n") - if err != nil { - return + if _, err := io.WriteString(w, "Content-Length: "); err != nil { + return err + } + if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil { + return err } } else if chunked(t.TransferEncoding) { - _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n") - if err != nil { - return + if _, err := io.WriteString(w, "Transfer-Encoding: chunked\r\n"); err != nil { + return err } } // Write Trailer header if t.Trailer != nil { - // TODO: At some point, there should be a generic mechanism for - // writing long headers, using HTTP line splitting - io.WriteString(w, "Trailer: ") - needComma := false + keys := make([]string, 0, len(t.Trailer)) for k := range t.Trailer { k = CanonicalHeaderKey(k) switch k { case "Transfer-Encoding", "Trailer", "Content-Length": return &badStringError{"invalid Trailer key", k} } - if needComma { - io.WriteString(w, ",") + keys = append(keys, k) + } + if len(keys) > 0 { + sort.Strings(keys) + // TODO: could do better allocation-wise here, but trailers are rare, + // so being lazy for now. + if _, err := io.WriteString(w, "Trailer: "+strings.Join(keys, ",")+"\r\n"); err != nil { + return err } - io.WriteString(w, k) - needComma = true } - _, err = io.WriteString(w, "\r\n") } - return + return nil } -func (t *transferWriter) WriteBody(w io.Writer) (err error) { +func (t *transferWriter) WriteBody(w io.Writer) error { + var err error var ncopy int64 // Write body @@ -217,11 +229,16 @@ func (t *transferWriter) WriteBody(w io.Writer) (err error) { // TODO(petar): Place trailer writer code here. if chunked(t.TransferEncoding) { + // Write Trailer header + if t.Trailer != nil { + if err := t.Trailer.Write(w); err != nil { + return err + } + } // Last chunk, empty trailer _, err = io.WriteString(w, "\r\n") } - - return + return err } type transferReader struct { @@ -253,6 +270,22 @@ func bodyAllowedForStatus(status int) bool { return true } +var ( + suppressedHeaders304 = []string{"Content-Type", "Content-Length", "Transfer-Encoding"} + suppressedHeadersNoBody = []string{"Content-Length", "Transfer-Encoding"} +) + +func suppressedHeaders(status int) []string { + switch { + case status == 304: + // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" + return suppressedHeaders304 + case !bodyAllowedForStatus(status): + return suppressedHeadersNoBody + } + return nil +} + // msg is *Request or *Response. func readTransfer(msg interface{}, r *bufio.Reader) (err error) { t := &transferReader{RequestMethod: "GET"} @@ -499,7 +532,7 @@ func fixTrailer(header Header, te []string) (Header, error) { case "Transfer-Encoding", "Trailer", "Content-Length": return nil, &badStringError{"bad trailer key", key} } - trailer.Del(key) + trailer[key] = nil } if len(trailer) == 0 { return nil, nil @@ -631,13 +664,23 @@ func (b *body) readTrailer() error { } switch rr := b.hdr.(type) { case *Request: - rr.Trailer = Header(hdr) + mergeSetHeader(&rr.Trailer, Header(hdr)) case *Response: - rr.Trailer = Header(hdr) + mergeSetHeader(&rr.Trailer, Header(hdr)) } return nil } +func mergeSetHeader(dst *Header, src Header) { + if *dst == nil { + *dst = src + return + } + for k, vv := range src { + (*dst)[k] = vv + } +} + func (b *body) Close() error { b.mu.Lock() defer b.mu.Unlock() diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go index 2c312a77a02..b1cc632a782 100644 --- a/libgo/go/net/http/transport.go +++ b/libgo/go/net/http/transport.go @@ -30,7 +30,14 @@ import ( // and caches them for reuse by subsequent calls. It uses HTTP proxies // as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and // $no_proxy) environment variables. -var DefaultTransport RoundTripper = &Transport{Proxy: ProxyFromEnvironment} +var DefaultTransport RoundTripper = &Transport{ + Proxy: ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, +} // DefaultMaxIdleConnsPerHost is the default value of Transport's // MaxIdleConnsPerHost. @@ -40,13 +47,13 @@ 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 - reqMu sync.Mutex - reqConn map[*Request]*persistConn - altMu sync.RWMutex - altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper + idleMu sync.Mutex + 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 // Proxy specifies a function to return a proxy for a given // Request. If the function returns a non-nil error, the @@ -63,6 +70,10 @@ type Transport struct { // tls.Client. If nil, the default configuration is used. TLSClientConfig *tls.Config + // TLSHandshakeTimeout specifies the maximum amount of time waiting to + // wait for a TLS handshake. Zero means no timeout. + TLSHandshakeTimeout time.Duration + // DisableKeepAlives, if true, prevents re-use of TCP connections // between different HTTP requests. DisableKeepAlives bool @@ -98,6 +109,9 @@ type Transport struct { // An error is returned if the proxy environment is invalid. // 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. +// +// 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() if proxy == "" { @@ -149,9 +163,11 @@ func (tr *transportRequest) extraHeaders() Header { // and redirects), see Get, Post, and the Client type. func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { if req.URL == nil { + req.closeBody() return nil, errors.New("http: nil Request.URL") } if req.Header == nil { + req.closeBody() return nil, errors.New("http: nil Request.Header") } if req.URL.Scheme != "http" && req.URL.Scheme != "https" { @@ -162,16 +178,19 @@ func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { } t.altMu.RUnlock() if rt == nil { + req.closeBody() return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} } return rt.RoundTrip(req) } if req.URL.Host == "" { + req.closeBody() return nil, errors.New("http: no Host in request URL") } treq := &transportRequest{Request: req} cm, err := t.connectMethodForRequest(treq) if err != nil { + req.closeBody() return nil, err } @@ -179,8 +198,10 @@ func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { // host (for http or https), the http proxy, or the http proxy // pre-CONNECTed to https server. In any case, we'll be ready // to send it requests. - pconn, err := t.getConn(cm) + pconn, err := t.getConn(req, cm) if err != nil { + t.setReqCanceler(req, nil) + req.closeBody() return nil, err } @@ -218,9 +239,6 @@ func (t *Transport) CloseIdleConnections() { t.idleConn = nil t.idleConnCh = nil t.idleMu.Unlock() - if m == nil { - return - } for _, conns := range m { for _, pconn := range conns { pconn.close() @@ -232,10 +250,10 @@ func (t *Transport) CloseIdleConnections() { // connection. func (t *Transport) CancelRequest(req *Request) { t.reqMu.Lock() - pc := t.reqConn[req] + cancel := t.reqCanceler[req] t.reqMu.Unlock() - if pc != nil { - pc.conn.Close() + if cancel != nil { + cancel() } } @@ -406,16 +424,16 @@ func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn) { } } -func (t *Transport) setReqConn(r *Request, pc *persistConn) { +func (t *Transport) setReqCanceler(r *Request, fn func()) { t.reqMu.Lock() defer t.reqMu.Unlock() - if t.reqConn == nil { - t.reqConn = make(map[*Request]*persistConn) + if t.reqCanceler == nil { + t.reqCanceler = make(map[*Request]func()) } - if pc != nil { - t.reqConn[r] = pc + if fn != nil { + t.reqCanceler[r] = fn } else { - delete(t.reqConn, r) + delete(t.reqCanceler, r) } } @@ -430,7 +448,7 @@ func (t *Transport) dial(network, addr string) (c net.Conn, err error) { // specified in the connectMethod. This includes doing a proxy CONNECT // and/or setting up TLS. If this doesn't return an error, the persistConn // is ready to write requests to. -func (t *Transport) getConn(cm connectMethod) (*persistConn, error) { +func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error) { if pc := t.getIdleConn(cm); pc != nil { return pc, nil } @@ -440,6 +458,16 @@ func (t *Transport) getConn(cm connectMethod) (*persistConn, error) { err error } dialc := make(chan dialRes) + + handlePendingDial := func() { + if v := <-dialc; v.err == nil { + t.putIdleConn(v.pc) + } + } + + cancelc := make(chan struct{}) + t.setReqCanceler(req, func() { close(cancelc) }) + go func() { pc, err := t.dialConn(cm) dialc <- dialRes{pc, err} @@ -456,12 +484,11 @@ func (t *Transport) getConn(cm connectMethod) (*persistConn, error) { // else's dial that they didn't use. // But our dial is still going, so give it away // when it finishes: - go func() { - if v := <-dialc; v.err == nil { - t.putIdleConn(v.pc) - } - }() + go handlePendingDial() return pc, nil + case <-cancelc: + go handlePendingDial() + return nil, errors.New("net/http: request canceled while waiting for connection") } } @@ -477,12 +504,13 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { pa := cm.proxyAuth() pconn := &persistConn{ - t: t, - cacheKey: cm.key(), - conn: conn, - reqch: make(chan requestAndChan, 50), - writech: make(chan writeRequest, 50), - closech: make(chan struct{}), + t: t, + cacheKey: cm.key(), + conn: conn, + reqch: make(chan requestAndChan, 1), + writech: make(chan writeRequest, 1), + closech: make(chan struct{}), + writeErrCh: make(chan error, 1), } switch { @@ -536,19 +564,38 @@ func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { cfg = &clone } } - conn = tls.Client(conn, cfg) - if err = conn.(*tls.Conn).Handshake(); err != nil { + plainConn := conn + tlsConn := tls.Client(plainConn, cfg) + errc := make(chan error, 2) + var timer *time.Timer // for canceling TLS handshake + if d := t.TLSHandshakeTimeout; d != 0 { + timer = time.AfterFunc(d, func() { + errc <- tlsHandshakeTimeoutError{} + }) + } + go func() { + err := tlsConn.Handshake() + if timer != nil { + timer.Stop() + } + errc <- err + }() + if err := <-errc; err != nil { + plainConn.Close() return nil, err } if !cfg.InsecureSkipVerify { - if err = conn.(*tls.Conn).VerifyHostname(cfg.ServerName); err != nil { + if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { + plainConn.Close() return nil, err } } - pconn.conn = conn + cs := tlsConn.ConnectionState() + pconn.tlsState = &cs + pconn.conn = tlsConn } - pconn.br = bufio.NewReader(pconn.conn) + pconn.br = bufio.NewReader(noteEOFReader{pconn.conn, &pconn.sawEOF}) pconn.bw = bufio.NewWriter(pconn.conn) go pconn.readLoop() go pconn.writeLoop() @@ -615,8 +662,8 @@ func useProxy(addr string) bool { // // Cache key form Description // ----------------- ------------------------- -// ||http|foo.com http directly to server, no proxy -// ||https|foo.com https directly to server, no proxy +// |http|foo.com http directly to server, no proxy +// |https|foo.com https directly to server, no proxy // http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com // http://proxy.com|http http to proxy, http to anywhere after that // @@ -680,16 +727,23 @@ type persistConn struct { t *Transport cacheKey connectMethodKey conn net.Conn - closed bool // whether conn has been closed + tlsState *tls.ConnectionState br *bufio.Reader // from conn + sawEOF bool // whether we've seen EOF from conn; owned by readLoop bw *bufio.Writer // to conn reqch chan requestAndChan // written by roundTrip; read by readLoop writech chan writeRequest // written by roundTrip; read by writeLoop - closech chan struct{} // broadcast close when readLoop (TCP connection) closes + closech chan struct{} // closed when conn closed isProxy bool + // writeErrCh passes the request write error (usually nil) + // from the writeLoop goroutine to the readLoop which passes + // it off to the res.Body reader, which then uses it to decide + // whether or not a connection can be reused. Issue 7569. + writeErrCh chan error - lk sync.Mutex // guards following 3 fields + lk sync.Mutex // guards following fields numExpectedResponses int + closed bool // whether conn has been closed broken bool // an error has happened on this connection; marked broken so it's not reused. // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the @@ -697,6 +751,7 @@ type persistConn struct { mutateHeaderFunc func(Header) } +// isBroken reports whether this connection is in a known broken state. func (pc *persistConn) isBroken() bool { pc.lk.Lock() b := pc.broken @@ -704,6 +759,10 @@ func (pc *persistConn) isBroken() bool { return b } +func (pc *persistConn) cancelRequest() { + pc.conn.Close() +} + var remoteSideClosedFunc func(error) bool // or nil to use default func remoteSideClosed(err error) bool { @@ -717,7 +776,6 @@ func remoteSideClosed(err error) bool { } func (pc *persistConn) readLoop() { - defer close(pc.closech) alive := true for alive { @@ -725,12 +783,14 @@ func (pc *persistConn) readLoop() { pc.lk.Lock() if pc.numExpectedResponses == 0 { - pc.closeLocked() - pc.lk.Unlock() - if len(pb) > 0 { - log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", - string(pb), err) + if !pc.closed { + pc.closeLocked() + if len(pb) > 0 { + log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", + string(pb), err) + } } + pc.lk.Unlock() return } pc.lk.Unlock() @@ -749,6 +809,11 @@ func (pc *persistConn) readLoop() { resp, err = ReadResponse(pc.br, rc.req) } } + + if resp != nil { + resp.TLS = pc.tlsState + } + hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0 if err != nil { @@ -758,13 +823,7 @@ func (pc *persistConn) readLoop() { resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") resp.ContentLength = -1 - gzReader, zerr := gzip.NewReader(resp.Body) - if zerr != nil { - pc.close() - err = zerr - } else { - resp.Body = &readerAndCloser{gzReader, resp.Body} - } + resp.Body = &gzipReader{body: resp.Body} } resp.Body = &bodyEOFSignal{body: resp.Body} } @@ -787,24 +846,18 @@ func (pc *persistConn) readLoop() { return nil } resp.Body.(*bodyEOFSignal).fn = func(err error) { - alive1 := alive - if err != nil { - alive1 = false - } - if alive1 && !pc.t.putIdleConn(pc) { - alive1 = false - } - if !alive1 || pc.isBroken() { - pc.close() - } - waitForBodyRead <- alive1 + waitForBodyRead <- alive && + err == nil && + !pc.sawEOF && + pc.wroteRequest() && + pc.t.putIdleConn(pc) } } if alive && !hasBody { - if !pc.t.putIdleConn(pc) { - alive = false - } + alive = !pc.sawEOF && + pc.wroteRequest() && + pc.t.putIdleConn(pc) } rc.ch <- responseAndError{resp, err} @@ -812,10 +865,14 @@ func (pc *persistConn) readLoop() { // Wait for the just-returned response body to be fully consumed // before we race and peek on the underlying bufio reader. if waitForBodyRead != nil { - alive = <-waitForBodyRead + select { + case alive = <-waitForBodyRead: + case <-pc.closech: + alive = false + } } - pc.t.setReqConn(rc.req, nil) + pc.t.setReqCanceler(rc.req, nil) if !alive { pc.close() @@ -837,14 +894,44 @@ func (pc *persistConn) writeLoop() { } if err != nil { pc.markBroken() + wr.req.Request.closeBody() } - wr.ch <- err + pc.writeErrCh <- err // to the body reader, which might recycle us + wr.ch <- err // to the roundTrip function case <-pc.closech: return } } } +// wroteRequest is a check before recycling a connection that the previous write +// (from writeLoop above) happened and was successful. +func (pc *persistConn) wroteRequest() bool { + select { + case err := <-pc.writeErrCh: + // Common case: the write happened well before the response, so + // avoid creating a timer. + return err == nil + default: + // Rare case: the request was written in writeLoop above but + // before it could send to pc.writeErrCh, the reader read it + // all, processed it, and called us here. In this case, give the + // write goroutine a bit of time to finish its send. + // + // Less rare case: We also get here in the legitimate case of + // Issue 7569, where the writer is still writing (or stalled), + // but the server has already replied. In this case, we don't + // want to wait too long, and we want to return false so this + // connection isn't re-used. + select { + case err := <-pc.writeErrCh: + return err == nil + case <-time.After(50 * time.Millisecond): + return false + } + } +} + type responseAndError struct { res *Response err error @@ -882,7 +969,7 @@ var errTimeout error = &httpError{err: "net/http: timeout awaiting response head var errClosed error = &httpError{err: "net/http: transport closed before response was received"} func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) { - pc.t.setReqConn(req.Request, pc) + pc.t.setReqCanceler(req.Request, pc.cancelRequest) pc.lk.Lock() pc.numExpectedResponses++ headerFn := pc.mutateHeaderFunc @@ -967,7 +1054,7 @@ WaitResponse: pc.lk.Unlock() if re.err != nil { - pc.t.setReqConn(req.Request, nil) + pc.t.setReqCanceler(req.Request, nil) } return re.res, re.err } @@ -992,6 +1079,7 @@ func (pc *persistConn) closeLocked() { if !pc.closed { pc.conn.Close() pc.closed = true + close(pc.closech) } pc.mutateHeaderFunc = nil } @@ -1074,7 +1162,47 @@ func (es *bodyEOFSignal) condfn(err error) { es.fn = nil } +// gzipReader wraps a response body so it can lazily +// call gzip.NewReader on the first call to Read +type gzipReader struct { + body io.ReadCloser // underlying Response.Body + zr io.Reader // lazily-initialized gzip reader +} + +func (gz *gzipReader) Read(p []byte) (n int, err error) { + if gz.zr == nil { + gz.zr, err = gzip.NewReader(gz.body) + if err != nil { + return 0, err + } + } + return gz.zr.Read(p) +} + +func (gz *gzipReader) Close() error { + return gz.body.Close() +} + type readerAndCloser struct { io.Reader io.Closer } + +type tlsHandshakeTimeoutError struct{} + +func (tlsHandshakeTimeoutError) Timeout() bool { return true } +func (tlsHandshakeTimeoutError) Temporary() bool { return true } +func (tlsHandshakeTimeoutError) Error() string { return "net/http: TLS handshake timeout" } + +type noteEOFReader struct { + r io.Reader + sawEOF *bool +} + +func (nr noteEOFReader) Read(p []byte) (n int, err error) { + n, err = nr.r.Read(p) + if err == io.EOF { + *nr.sawEOF = true + } + return +} diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index 2678d71b1de..964ca0fca54 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -11,9 +11,12 @@ import ( "bytes" "compress/gzip" "crypto/rand" + "crypto/tls" + "errors" "fmt" "io" "io/ioutil" + "log" "net" "net/http" . "net/http" @@ -54,21 +57,21 @@ func (c *testCloseConn) Close() error { // 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 - mutex sync.Mutex } func (tcs *testConnSet) insert(c net.Conn) { - tcs.mutex.Lock() - defer tcs.mutex.Unlock() + 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.mutex.Lock() - defer tcs.mutex.Unlock() + tcs.mu.Lock() + defer tcs.mu.Unlock() tcs.closed[c] = true } @@ -91,11 +94,19 @@ func makeTestDial(t *testing.T) (*testConnSet, func(n, addr string) (net.Conn, e } func (tcs *testConnSet) check(t *testing.T) { - tcs.mutex.Lock() - defer tcs.mutex.Unlock() - - for i, c := range tcs.list { - if !tcs.closed[c] { + 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)) } } @@ -347,10 +358,11 @@ func TestTransportMaxPerHostIdleConns(t *testing.T) { resp, err := c.Get(ts.URL) if err != nil { t.Error(err) + return } - _, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("ReadAll: %v", err) + if _, err := ioutil.ReadAll(resp.Body); err != nil { + t.Errorf("ReadAll: %v", err) + return } donech <- true } @@ -791,6 +803,33 @@ func TestTransportGzipRecursive(t *testing.T) { } } +// 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" { @@ -1211,9 +1250,13 @@ func TestTransportResponseHeaderTimeout(t *testing.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) {}) + 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) @@ -1236,6 +1279,12 @@ func TestTransportResponseHeaderTimeout(t *testing.T) { } 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 { @@ -1321,6 +1370,60 @@ func TestTransportCancelRequest(t *testing.T) { } } +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) + + 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) + } +} + // 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. @@ -1450,8 +1553,10 @@ func TestTransportSocketLateBinding(t *testing.T) { dialGate := make(chan bool, 1) tr := &Transport{ Dial: func(n, addr string) (net.Conn, error) { - <-dialGate - return net.Dial(n, addr) + if <-dialGate { + return net.Dial(n, addr) + } + return nil, errors.New("manually closed") }, DisableKeepAlives: false, } @@ -1486,7 +1591,7 @@ func TestTransportSocketLateBinding(t *testing.T) { t.Fatalf("/foo came from conn %q; /bar came from %q instead", fooAddr, barAddr) } barRes.Body.Close() - dialGate <- true + dialGate <- false } // Issue 2184 @@ -1637,10 +1742,7 @@ var proxyFromEnvTests = []proxyFromEnvTest{ } func TestProxyFromEnvironment(t *testing.T) { - os.Setenv("HTTP_PROXY", "") - os.Setenv("http_proxy", "") - os.Setenv("NO_PROXY", "") - os.Setenv("no_proxy", "") + ResetProxyEnv() for _, tt := range proxyFromEnvTests { os.Setenv("HTTP_PROXY", tt.env) os.Setenv("NO_PROXY", tt.noenv) @@ -1722,6 +1824,308 @@ func TestTransportClosesRequestBody(t *testing.T) { } } +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") + } +} + +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 http://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 wantBody(res *http.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 diff --git a/libgo/go/net/interface.go b/libgo/go/net/interface.go index 0713e9cd6a9..2e9f1ebc679 100644 --- a/libgo/go/net/interface.go +++ b/libgo/go/net/interface.go @@ -7,11 +7,11 @@ package net import "errors" var ( - errInvalidInterface = errors.New("net: invalid interface") - errInvalidInterfaceIndex = errors.New("net: invalid interface index") - errInvalidInterfaceName = errors.New("net: invalid interface name") - errNoSuchInterface = errors.New("net: no such interface") - errNoSuchMulticastInterface = errors.New("net: no such multicast interface") + errInvalidInterface = errors.New("invalid network interface") + errInvalidInterfaceIndex = errors.New("invalid network interface index") + errInvalidInterfaceName = errors.New("invalid network interface name") + errNoSuchInterface = errors.New("no such network interface") + errNoSuchMulticastInterface = errors.New("no such multicast network interface") ) // Interface represents a mapping between network interface name diff --git a/libgo/go/net/interface_stub.go b/libgo/go/net/interface_stub.go index a4eb731da44..c38fb7f7651 100644 --- a/libgo/go/net/interface_stub.go +++ b/libgo/go/net/interface_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build plan9 +// +build nacl plan9 solaris package net diff --git a/libgo/go/net/ipraw_test.go b/libgo/go/net/ipraw_test.go index ea183f1d3eb..0632dafc65e 100644 --- a/libgo/go/net/ipraw_test.go +++ b/libgo/go/net/ipraw_test.go @@ -247,7 +247,7 @@ var ipConnLocalNameTests = []struct { func TestIPConnLocalName(t *testing.T) { switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) default: if os.Getuid() != 0 { @@ -277,7 +277,7 @@ func TestIPConnRemoteName(t *testing.T) { } } - raddr := &IPAddr{IP: IPv4(127, 0, 0, 10).To4()} + raddr := &IPAddr{IP: IPv4(127, 0, 0, 1).To4()} c, err := DialIP("ip:tcp", &IPAddr{IP: IPv4(127, 0, 0, 1)}, raddr) if err != nil { t.Fatalf("DialIP failed: %v", err) diff --git a/libgo/go/net/iprawsock_posix.go b/libgo/go/net/iprawsock_posix.go index a1a008ac413..bbb3f3ed66c 100644 --- a/libgo/go/net/iprawsock_posix.go +++ b/libgo/go/net/iprawsock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net @@ -79,7 +79,7 @@ func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error) { // TODO(cw,rsc): consider using readv if we know the family // type to avoid the header trim/copy var addr *IPAddr - n, sa, err := c.fd.ReadFrom(b) + n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &IPAddr{IP: sa.Addr[0:]} @@ -112,7 +112,7 @@ func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err return 0, 0, 0, nil, syscall.EINVAL } var sa syscall.Sockaddr - n, oobn, flags, sa, err = c.fd.ReadMsg(b, oob) + n, oobn, flags, sa, err = c.fd.readMsg(b, oob) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &IPAddr{IP: sa.Addr[0:]} @@ -133,6 +133,9 @@ func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) { if !c.ok() { return 0, syscall.EINVAL } + if c.fd.isConnected { + return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + } if addr == nil { return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} } @@ -140,7 +143,7 @@ func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) { if err != nil { return 0, &OpError{"write", c.fd.net, addr, err} } - return c.fd.WriteTo(b, sa) + return c.fd.writeTo(b, sa) } // WriteTo implements the PacketConn WriteTo method. @@ -162,6 +165,9 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error if !c.ok() { return 0, 0, syscall.EINVAL } + if c.fd.isConnected { + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + } if addr == nil { return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} } @@ -169,7 +175,7 @@ func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error if err != nil { return 0, 0, &OpError{"write", c.fd.net, addr, err} } - return c.fd.WriteMsg(b, oob, sa) + return c.fd.writeMsg(b, oob, sa) } // DialIP connects to the remote address raddr on the network protocol diff --git a/libgo/go/net/ipsock.go b/libgo/go/net/ipsock.go index 8b586ef7c3e..dda85780308 100644 --- a/libgo/go/net/ipsock.go +++ b/libgo/go/net/ipsock.go @@ -16,7 +16,7 @@ var ( // networking functionality. supportsIPv4 bool - // supportsIPv6 reports whether the platfrom supports IPv6 + // supportsIPv6 reports whether the platform supports IPv6 // networking functionality. supportsIPv6 bool @@ -207,7 +207,7 @@ missingBrackets: } func splitHostZone(s string) (host, zone string) { - // The IPv6 scoped addressing zone identifer starts after the + // The IPv6 scoped addressing zone identifier starts after the // last percent sign. if i := last(s, '%'); i > 0 { host, zone = s[:i], s[i+1:] @@ -232,7 +232,7 @@ func JoinHostPort(host, port string) string { // address or a DNS name and returns an internet protocol family // address. It returns a list that contains a pair of different // address family addresses when addr is a DNS name and the name has -// mutiple address family records. The result contains at least one +// multiple address family records. The result contains at least one // address when error is nil. func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) { var ( diff --git a/libgo/go/net/ipsock_plan9.go b/libgo/go/net/ipsock_plan9.go index 914ed50826f..94ceea31b03 100644 --- a/libgo/go/net/ipsock_plan9.go +++ b/libgo/go/net/ipsock_plan9.go @@ -60,12 +60,12 @@ func parsePlan9Addr(s string) (ip IP, iport int, err error) { if i >= 0 { addr = ParseIP(s[:i]) if addr == nil { - return nil, 0, errors.New("net: parsing IP failed") + return nil, 0, errors.New("parsing IP failed") } } p, _, ok := dtoi(s[i+1:], 0) if !ok { - return nil, 0, errors.New("net: parsing port failed") + return nil, 0, errors.New("parsing port failed") } if p < 0 || p > 0xFFFF { return nil, 0, &AddrError{"invalid port", string(p)} diff --git a/libgo/go/net/ipsock_posix.go b/libgo/go/net/ipsock_posix.go index a83e5256174..2ba4c8efd53 100644 --- a/libgo/go/net/ipsock_posix.go +++ b/libgo/go/net/ipsock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows // Internet protocol family sockets for POSIX @@ -40,12 +40,13 @@ func probeIPv4Stack() bool { func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) { var probes = []struct { laddr TCPAddr + value int ok bool }{ // IPv6 communication capability - {TCPAddr{IP: ParseIP("::1")}, false}, + {laddr: TCPAddr{IP: ParseIP("::1")}, value: 1}, // IPv6 IPv4-mapped address communication capability - {TCPAddr{IP: IPv4(127, 0, 0, 1)}, false}, + {laddr: TCPAddr{IP: IPv4(127, 0, 0, 1)}, value: 0}, } for i := range probes { @@ -54,7 +55,7 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) { continue } defer closesocket(s) - syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0) + syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, probes[i].value) sa, err := probes[i].laddr.sockaddr(syscall.AF_INET6) if err != nil { continue diff --git a/libgo/go/net/lookup_plan9.go b/libgo/go/net/lookup_plan9.go index 2ccd997c2cb..b80ac10e0d9 100644 --- a/libgo/go/net/lookup_plan9.go +++ b/libgo/go/net/lookup_plan9.go @@ -63,7 +63,7 @@ func queryCS1(net string, ip IP, port int) (clone, dest string, err error) { } f := getFields(lines[0]) if len(f) < 2 { - return "", "", errors.New("net: bad response from ndb/cs") + return "", "", errors.New("bad response from ndb/cs") } clone, dest = f[0], f[1] return @@ -199,7 +199,7 @@ func lookupCNAME(name string) (cname string, err error) { return f[2] + ".", nil } } - return "", errors.New("net: bad response from ndb/dns") + return "", errors.New("bad response from ndb/dns") } func lookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) { diff --git a/libgo/go/net/lookup_unix.go b/libgo/go/net/lookup_unix.go index 59e9f63210c..b1d2f8f31a9 100644 --- a/libgo/go/net/lookup_unix.go +++ b/libgo/go/net/lookup_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris package net diff --git a/libgo/go/net/mail/message.go b/libgo/go/net/mail/message.go index 4b332c1b5be..ba0778caa73 100644 --- a/libgo/go/net/mail/message.go +++ b/libgo/go/net/mail/message.go @@ -363,7 +363,7 @@ func (p *addrParser) consumePhrase() (phrase string, err error) { // Ignore any error if we got at least one word. if err != nil && len(words) == 0 { debug.Printf("consumePhrase: hit err: %v", err) - return "", errors.New("mail: missing word in phrase") + return "", fmt.Errorf("mail: missing word in phrase: %v", err) } phrase = strings.Join(words, " ") return phrase, nil @@ -442,11 +442,11 @@ func (p *addrParser) len() int { func decodeRFC2047Word(s string) (string, error) { fields := strings.Split(s, "?") if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" { - return "", errors.New("mail: address not RFC 2047 encoded") + return "", errors.New("address not RFC 2047 encoded") } charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) if charset != "iso-8859-1" && charset != "utf-8" { - return "", fmt.Errorf("mail: charset not supported: %q", charset) + return "", fmt.Errorf("charset not supported: %q", charset) } in := bytes.NewBufferString(fields[3]) @@ -457,7 +457,7 @@ func decodeRFC2047Word(s string) (string, error) { case "q": r = qDecoder{r: in} default: - return "", fmt.Errorf("mail: RFC 2047 encoding not supported: %q", enc) + return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc) } dec, err := ioutil.ReadAll(r) diff --git a/libgo/go/net/mail/message_test.go b/libgo/go/net/mail/message_test.go index 1bb4e8bc401..eb9c8cbdc9b 100644 --- a/libgo/go/net/mail/message_test.go +++ b/libgo/go/net/mail/message_test.go @@ -8,6 +8,7 @@ import ( "bytes" "io/ioutil" "reflect" + "strings" "testing" "time" ) @@ -116,6 +117,14 @@ func TestDateParsing(t *testing.T) { } } +func TestAddressParsingError(t *testing.T) { + const txt = "=?iso-8859-2?Q?Bogl=E1rka_Tak=E1cs?= <unknown@gmail.com>" + _, err := ParseAddress(txt) + if err == nil || !strings.Contains(err.Error(), "charset not supported") { + t.Errorf(`mail.ParseAddress(%q) err: %q, want ".*charset not supported.*"`, txt, err) + } +} + func TestAddressParsing(t *testing.T) { tests := []struct { addrsStr string diff --git a/libgo/go/net/multicast_test.go b/libgo/go/net/multicast_test.go index 5660fd42f8c..63dbce88e9a 100644 --- a/libgo/go/net/multicast_test.go +++ b/libgo/go/net/multicast_test.go @@ -25,8 +25,10 @@ var ipv4MulticastListenerTests = []struct { // port. func TestIPv4MulticastListener(t *testing.T) { switch runtime.GOOS { - case "plan9": + case "nacl", "plan9": t.Skipf("skipping test on %q", runtime.GOOS) + case "solaris": + t.Skipf("skipping test on solaris, see issue 7399") } closer := func(cs []*UDPConn) { @@ -93,8 +95,10 @@ var ipv6MulticastListenerTests = []struct { // port. func TestIPv6MulticastListener(t *testing.T) { switch runtime.GOOS { - case "plan9", "solaris": + case "plan9": t.Skipf("skipping test on %q", runtime.GOOS) + case "solaris": + t.Skipf("skipping test on solaris, see issue 7399") } if !supportsIPv6 { t.Skip("ipv6 is not supported") diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index 2e6db555143..ca56af54fc6 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -275,7 +275,16 @@ type Listener interface { Addr() Addr } -var errMissingAddress = errors.New("missing address") +// Various errors contained in OpError. +var ( + // For connection setup and write operations. + errMissingAddress = errors.New("missing address") + + // For both read and write operations. + errTimeout error = &timeoutError{} + errClosing = errors.New("use of closed network connection") + ErrWriteToConnected = errors.New("use of WriteTo with pre-connected connection") +) // OpError is the error type usually returned by functions in the net // package. It describes the operation, network type, and address of @@ -337,10 +346,6 @@ func (e *timeoutError) Error() string { return "i/o timeout" } func (e *timeoutError) Timeout() bool { return true } func (e *timeoutError) Temporary() bool { return true } -var errTimeout error = &timeoutError{} - -var errClosing = errors.New("use of closed network connection") - type AddrError struct { Err string Addr string diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go index c9fb433ec91..bfed4d657fd 100644 --- a/libgo/go/net/net_test.go +++ b/libgo/go/net/net_test.go @@ -28,12 +28,14 @@ func TestShutdown(t *testing.T) { defer ln.Close() c, err := ln.Accept() if err != nil { - t.Fatalf("Accept: %v", err) + t.Errorf("Accept: %v", err) + return } var buf [10]byte n, err := c.Read(buf[:]) if n != 0 || err != io.EOF { - t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err) + t.Errorf("server Read = %d, %v; want 0, io.EOF", n, err) + return } c.Write([]byte("response")) c.Close() @@ -62,7 +64,7 @@ func TestShutdown(t *testing.T) { func TestShutdownUnix(t *testing.T) { switch runtime.GOOS { - case "windows", "plan9": + case "nacl", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } f, err := ioutil.TempFile("", "go_net_unixtest") @@ -84,12 +86,14 @@ func TestShutdownUnix(t *testing.T) { go func() { c, err := ln.Accept() if err != nil { - t.Fatalf("Accept: %v", err) + t.Errorf("Accept: %v", err) + return } var buf [10]byte n, err := c.Read(buf[:]) if n != 0 || err != io.EOF { - t.Fatalf("server Read = %d, %v; want 0, io.EOF", n, err) + t.Errorf("server Read = %d, %v; want 0, io.EOF", n, err) + return } c.Write([]byte("response")) c.Close() @@ -196,7 +200,8 @@ func TestTCPClose(t *testing.T) { go func() { c, err := Dial("tcp", l.Addr().String()) if err != nil { - t.Fatal(err) + t.Errorf("Dial: %v", err) + return } go read(c) diff --git a/libgo/go/net/packetconn_test.go b/libgo/go/net/packetconn_test.go index 945003f67ad..b6e4e76f930 100644 --- a/libgo/go/net/packetconn_test.go +++ b/libgo/go/net/packetconn_test.go @@ -15,12 +15,6 @@ import ( "time" ) -func strfunc(s string) func() string { - return func() string { - return s - } -} - func packetConnTestData(t *testing.T, net string, i int) ([]byte, func()) { switch net { case "udp": @@ -46,7 +40,7 @@ func packetConnTestData(t *testing.T, net string, i int) ([]byte, func()) { return b, nil case "unixgram": switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": return nil, func() { t.Logf("skipping %q test on %q", net, runtime.GOOS) } @@ -62,12 +56,12 @@ func packetConnTestData(t *testing.T, net string, i int) ([]byte, func()) { var packetConnTests = []struct { net string - addr1 func() string - addr2 func() string + addr1 string + addr2 string }{ - {"udp", strfunc("127.0.0.1:0"), strfunc("127.0.0.1:0")}, - {"ip:icmp", strfunc("127.0.0.1"), strfunc("127.0.0.1")}, - {"unixgram", testUnixAddr, testUnixAddr}, + {"udp", "127.0.0.1:0", "127.0.0.1:0"}, + {"ip:icmp", "127.0.0.1", "127.0.0.1"}, + {"unixgram", testUnixAddr(), testUnixAddr()}, } func TestPacketConn(t *testing.T) { @@ -88,22 +82,21 @@ func TestPacketConn(t *testing.T) { continue } - addr1, addr2 := tt.addr1(), tt.addr2() - c1, err := ListenPacket(tt.net, addr1) + c1, err := ListenPacket(tt.net, tt.addr1) if err != nil { t.Fatalf("ListenPacket failed: %v", err) } - defer closer(c1, netstr[0], addr1, addr2) + defer closer(c1, netstr[0], tt.addr1, tt.addr2) c1.LocalAddr() c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) - c2, err := ListenPacket(tt.net, addr2) + c2, err := ListenPacket(tt.net, tt.addr2) if err != nil { t.Fatalf("ListenPacket failed: %v", err) } - defer closer(c2, netstr[0], addr1, addr2) + defer closer(c2, netstr[0], tt.addr1, tt.addr2) c2.LocalAddr() c2.SetDeadline(time.Now().Add(100 * time.Millisecond)) c2.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) @@ -145,12 +138,11 @@ func TestConnAndPacketConn(t *testing.T) { continue } - addr1, addr2 := tt.addr1(), tt.addr2() - c1, err := ListenPacket(tt.net, addr1) + c1, err := ListenPacket(tt.net, tt.addr1) if err != nil { t.Fatalf("ListenPacket failed: %v", err) } - defer closer(c1, netstr[0], addr1, addr2) + defer closer(c1, netstr[0], tt.addr1, tt.addr2) c1.LocalAddr() c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) c1.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) diff --git a/libgo/go/net/port_unix.go b/libgo/go/net/port_unix.go index 3cd9ca2aa71..89558c1f029 100644 --- a/libgo/go/net/port_unix.go +++ b/libgo/go/net/port_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris // Read system port mappings from /etc/services @@ -10,12 +10,16 @@ package net import "sync" -var services map[string]map[string]int +// services contains minimal mappings between services names and port +// numbers for platforms that don't have a complete list of port numbers +// (some Solaris distros). +var services = map[string]map[string]int{ + "tcp": {"http": 80}, +} var servicesError error var onceReadServices sync.Once func readServices() { - services = make(map[string]map[string]int) var file *file if file, servicesError = open("/etc/services"); servicesError != nil { return @@ -29,7 +33,7 @@ func readServices() { if len(f) < 2 { continue } - portnet := f[1] // "tcp/80" + portnet := f[1] // "80/tcp" port, j, ok := dtoi(portnet, 0) if !ok || port <= 0 || j >= len(portnet) || portnet[j] != '/' { continue diff --git a/libgo/go/net/protoconn_test.go b/libgo/go/net/protoconn_test.go index 5a8958b0866..12856b6c311 100644 --- a/libgo/go/net/protoconn_test.go +++ b/libgo/go/net/protoconn_test.go @@ -19,7 +19,7 @@ import ( // also uses /tmp directory in case it is prohibited to create UNIX // sockets in TMPDIR. func testUnixAddr() string { - f, err := ioutil.TempFile("/tmp", "nettest") + f, err := ioutil.TempFile("", "nettest") if err != nil { panic(err) } @@ -236,7 +236,7 @@ func TestIPConnSpecificMethods(t *testing.T) { func TestUnixListenerSpecificMethods(t *testing.T) { switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } @@ -278,7 +278,7 @@ func TestUnixListenerSpecificMethods(t *testing.T) { func TestUnixConnSpecificMethods(t *testing.T) { switch runtime.GOOS { - case "plan9", "windows": + case "nacl", "plan9", "windows": t.Skipf("skipping test on %q", runtime.GOOS) } diff --git a/libgo/go/net/rpc/client.go b/libgo/go/net/rpc/client.go index c524d0a0a2d..21f79b06844 100644 --- a/libgo/go/net/rpc/client.go +++ b/libgo/go/net/rpc/client.go @@ -39,14 +39,16 @@ type Call struct { // with a single Client, and a Client may be used by // multiple goroutines simultaneously. type Client struct { - mutex sync.Mutex // protects pending, seq, request - sending sync.Mutex + codec ClientCodec + + sending sync.Mutex + + mutex sync.Mutex // protects following request Request seq uint64 - codec ClientCodec pending map[uint64]*Call - closing bool - shutdown bool + closing bool // user has called Close + shutdown bool // server has told us to stop } // A ClientCodec implements writing of RPC requests and @@ -274,7 +276,7 @@ func Dial(network, address string) (*Client, error) { func (client *Client) Close() error { client.mutex.Lock() - if client.shutdown || client.closing { + if client.closing { client.mutex.Unlock() return ErrShutdown } diff --git a/libgo/go/net/rpc/client_test.go b/libgo/go/net/rpc/client_test.go new file mode 100644 index 00000000000..bbfc1ec3a3e --- /dev/null +++ b/libgo/go/net/rpc/client_test.go @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +package rpc + +import ( + "errors" + "testing" +) + +type shutdownCodec struct { + responded chan int + closed bool +} + +func (c *shutdownCodec) WriteRequest(*Request, interface{}) error { return nil } +func (c *shutdownCodec) ReadResponseBody(interface{}) error { return nil } +func (c *shutdownCodec) ReadResponseHeader(*Response) error { + c.responded <- 1 + return errors.New("shutdownCodec ReadResponseHeader") +} +func (c *shutdownCodec) Close() error { + c.closed = true + return nil +} + +func TestCloseCodec(t *testing.T) { + codec := &shutdownCodec{responded: make(chan int)} + client := NewClientWithCodec(codec) + <-codec.responded + client.Close() + if !codec.closed { + t.Error("client.Close did not close codec") + } +} diff --git a/libgo/go/net/rpc/jsonrpc/all_test.go b/libgo/go/net/rpc/jsonrpc/all_test.go index 40d4b82d7f2..a433a365e88 100644 --- a/libgo/go/net/rpc/jsonrpc/all_test.go +++ b/libgo/go/net/rpc/jsonrpc/all_test.go @@ -5,6 +5,7 @@ package jsonrpc import ( + "bytes" "encoding/json" "errors" "fmt" @@ -12,6 +13,7 @@ import ( "io/ioutil" "net" "net/rpc" + "strings" "testing" ) @@ -202,6 +204,39 @@ func TestMalformedOutput(t *testing.T) { } } +func TestServerErrorHasNullResult(t *testing.T) { + var out bytes.Buffer + sc := NewServerCodec(struct { + io.Reader + io.Writer + io.Closer + }{ + Reader: strings.NewReader(`{"method": "Arith.Add", "id": "123", "params": []}`), + Writer: &out, + Closer: ioutil.NopCloser(nil), + }) + r := new(rpc.Request) + if err := sc.ReadRequestHeader(r); err != nil { + t.Fatal(err) + } + const valueText = "the value we don't want to see" + const errorText = "some error" + err := sc.WriteResponse(&rpc.Response{ + ServiceMethod: "Method", + Seq: 1, + Error: errorText, + }, valueText) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(out.String(), errorText) { + t.Fatalf("Response didn't contain expected error %q: %s", errorText, &out) + } + if strings.Contains(out.String(), valueText) { + t.Errorf("Response contains both an error and value: %s", &out) + } +} + func TestUnexpectedError(t *testing.T) { cli, srv := myPipe() go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error diff --git a/libgo/go/net/rpc/jsonrpc/server.go b/libgo/go/net/rpc/jsonrpc/server.go index 16ec0fe9ad5..e6d37cfa64f 100644 --- a/libgo/go/net/rpc/jsonrpc/server.go +++ b/libgo/go/net/rpc/jsonrpc/server.go @@ -100,7 +100,6 @@ func (c *serverCodec) ReadRequestBody(x interface{}) error { var null = json.RawMessage([]byte("null")) func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error { - var resp serverResponse c.mutex.Lock() b, ok := c.pending[r.Seq] if !ok { @@ -114,10 +113,9 @@ func (c *serverCodec) WriteResponse(r *rpc.Response, x interface{}) error { // Invalid request so no id. Use JSON null. b = &null } - resp.Id = b - resp.Result = x + resp := serverResponse{Id: b} if r.Error == "" { - resp.Error = nil + resp.Result = x } else { resp.Error = r.Error } diff --git a/libgo/go/net/rpc/server_test.go b/libgo/go/net/rpc/server_test.go index 3b9a88380cf..0dc4ddc2de0 100644 --- a/libgo/go/net/rpc/server_test.go +++ b/libgo/go/net/rpc/server_test.go @@ -594,7 +594,6 @@ func TestErrorAfterClientClose(t *testing.T) { } func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) { - b.StopTimer() once.Do(startServer) client, err := dial() if err != nil { @@ -604,33 +603,24 @@ func benchmarkEndToEnd(dial func() (*Client, error), b *testing.B) { // Synchronous calls args := &Args{7, 8} - procs := runtime.GOMAXPROCS(-1) - N := int32(b.N) - var wg sync.WaitGroup - wg.Add(procs) - b.StartTimer() - - for p := 0; p < procs; p++ { - go func() { - reply := new(Reply) - for atomic.AddInt32(&N, -1) >= 0 { - err := client.Call("Arith.Add", args, reply) - if err != nil { - b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error()) - } - if reply.C != args.A+args.B { - b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B) - } + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + reply := new(Reply) + for pb.Next() { + err := client.Call("Arith.Add", args, reply) + if err != nil { + b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error()) } - wg.Done() - }() - } - wg.Wait() + if reply.C != args.A+args.B { + b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B) + } + } + }) } func benchmarkEndToEndAsync(dial func() (*Client, error), b *testing.B) { const MaxConcurrentCalls = 100 - b.StopTimer() once.Do(startServer) client, err := dial() if err != nil { @@ -647,7 +637,7 @@ func benchmarkEndToEndAsync(dial func() (*Client, error), b *testing.B) { wg.Add(procs) gate := make(chan bool, MaxConcurrentCalls) res := make(chan *Call, MaxConcurrentCalls) - b.StartTimer() + b.ResetTimer() for p := 0; p < procs; p++ { go func() { diff --git a/libgo/go/net/sendfile_dragonfly.go b/libgo/go/net/sendfile_dragonfly.go index a2219c16337..bc88fd3b907 100644 --- a/libgo/go/net/sendfile_dragonfly.go +++ b/libgo/go/net/sendfile_dragonfly.go @@ -23,7 +23,7 @@ const maxSendfileSize int = 4 << 20 // if handled == false, sendFile performed no work. func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { // DragonFly uses 0 as the "until EOF" value. If you pass in more bytes than the - // file contains, it will loop back to the beginning ad nauseum until it's sent + // file contains, it will loop back to the beginning ad nauseam until it's sent // exactly the number of bytes told to. As such, we need to know exactly how many // bytes to send. var remain int64 = 0 diff --git a/libgo/go/net/sendfile_freebsd.go b/libgo/go/net/sendfile_freebsd.go index 42fe799efbd..ffc147262a8 100644 --- a/libgo/go/net/sendfile_freebsd.go +++ b/libgo/go/net/sendfile_freebsd.go @@ -23,7 +23,7 @@ const maxSendfileSize int = 4 << 20 // if handled == false, sendFile performed no work. func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) { // FreeBSD uses 0 as the "until EOF" value. If you pass in more bytes than the - // file contains, it will loop back to the beginning ad nauseum until it's sent + // file contains, it will loop back to the beginning ad nauseam until it's sent // exactly the number of bytes told to. As such, we need to know exactly how many // bytes to send. var remain int64 = 0 diff --git a/libgo/go/net/sendfile_stub.go b/libgo/go/net/sendfile_stub.go index 3660849c182..03426ef0df1 100644 --- a/libgo/go/net/sendfile_stub.go +++ b/libgo/go/net/sendfile_stub.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin netbsd openbsd +// +build darwin nacl netbsd openbsd solaris package net diff --git a/libgo/go/net/server_test.go b/libgo/go/net/server_test.go index 9194a8ec24d..6a2bb924329 100644 --- a/libgo/go/net/server_test.go +++ b/libgo/go/net/server_test.go @@ -9,21 +9,20 @@ import ( "io" "os" "runtime" - "strconv" "testing" "time" ) -func skipServerTest(net, unixsotype, addr string, ipv6, ipv4map, linuxonly bool) bool { +func skipServerTest(net, unixsotype, addr string, ipv6, ipv4map, linuxOnly bool) bool { switch runtime.GOOS { case "linux": - case "plan9", "windows": + case "nacl", "plan9", "windows": // "unix" sockets are not supported on Windows and Plan 9. if net == unixsotype { return true } default: - if net == unixsotype && linuxonly { + if net == unixsotype && linuxOnly { return true } } @@ -42,21 +41,15 @@ func skipServerTest(net, unixsotype, addr string, ipv6, ipv4map, linuxonly bool) return false } -func tempfile(filename string) string { - // use /tmp in case it is prohibited to create - // UNIX sockets in TMPDIR - return "/tmp/" + filename + "." + strconv.Itoa(os.Getpid()) -} - var streamConnServerTests = []struct { - snet string // server side - saddr string - cnet string // client side - caddr string - ipv6 bool // test with underlying AF_INET6 socket - ipv4map bool // test with IPv6 IPv4-mapping functionality - empty bool // test with empty data - linux bool // test with abstract unix domain socket, a Linux-ism + snet string // server side + saddr string + cnet string // client side + caddr string + ipv6 bool // test with underlying AF_INET6 socket + ipv4map bool // test with IPv6 IPv4-mapping functionality + empty bool // test with empty data + linuxOnly bool // test with abstract unix domain socket, a Linux-ism }{ {snet: "tcp", saddr: "", cnet: "tcp", caddr: "127.0.0.1"}, {snet: "tcp", saddr: "0.0.0.0", cnet: "tcp", caddr: "127.0.0.1"}, @@ -93,13 +86,13 @@ var streamConnServerTests = []struct { {snet: "tcp6", saddr: "[::1]", cnet: "tcp6", caddr: "[::1]", ipv6: true}, - {snet: "unix", saddr: tempfile("gotest1.net"), cnet: "unix", caddr: tempfile("gotest1.net.local")}, - {snet: "unix", saddr: "@gotest2/net", cnet: "unix", caddr: "@gotest2/net.local", linux: true}, + {snet: "unix", saddr: testUnixAddr(), cnet: "unix", caddr: testUnixAddr()}, + {snet: "unix", saddr: "@gotest2/net", cnet: "unix", caddr: "@gotest2/net.local", linuxOnly: true}, } func TestStreamConnServer(t *testing.T) { for _, tt := range streamConnServerTests { - if skipServerTest(tt.snet, "unix", tt.saddr, tt.ipv6, tt.ipv4map, tt.linux) { + if skipServerTest(tt.snet, "unix", tt.saddr, tt.ipv6, tt.ipv4map, tt.linuxOnly) { continue } @@ -137,21 +130,28 @@ func TestStreamConnServer(t *testing.T) { } var seqpacketConnServerTests = []struct { - net string - saddr string // server address - caddr string // client address - empty bool // test with empty data + net string + saddr string // server address + caddr string // client address + empty bool // test with empty data + linuxOnly bool // test with abstract unix domain socket, a Linux-ism }{ - {net: "unixpacket", saddr: tempfile("/gotest3.net"), caddr: tempfile("gotest3.net.local")}, - {net: "unixpacket", saddr: "@gotest4/net", caddr: "@gotest4/net.local"}, + {net: "unixpacket", saddr: testUnixAddr(), caddr: testUnixAddr()}, + {net: "unixpacket", saddr: "@gotest4/net", caddr: "@gotest4/net.local", linuxOnly: true}, } func TestSeqpacketConnServer(t *testing.T) { - if runtime.GOOS != "linux" { + switch runtime.GOOS { + case "darwin", "nacl", "openbsd", "plan9", "windows": + fallthrough + case "freebsd": // FreeBSD 8 doesn't support unixpacket t.Skipf("skipping test on %q", runtime.GOOS) } for _, tt := range seqpacketConnServerTests { + if runtime.GOOS != "linux" && tt.linuxOnly { + continue + } listening := make(chan string) done := make(chan int) switch tt.net { @@ -248,15 +248,15 @@ func runStreamConnClient(t *testing.T, net, taddr string, isEmpty bool) { var testDatagram = flag.Bool("datagram", false, "whether to test udp and unixgram") var datagramPacketConnServerTests = []struct { - snet string // server side - saddr string - cnet string // client side - caddr string - ipv6 bool // test with underlying AF_INET6 socket - ipv4map bool // test with IPv6 IPv4-mapping functionality - dial bool // test with Dial or DialUnix - empty bool // test with empty data - linux bool // test with abstract unix domain socket, a Linux-ism + snet string // server side + saddr string + cnet string // client side + caddr string + ipv6 bool // test with underlying AF_INET6 socket + ipv4map bool // test with IPv6 IPv4-mapping functionality + dial bool // test with Dial or DialUnix + empty bool // test with empty data + linuxOnly bool // test with abstract unix domain socket, a Linux-ism }{ {snet: "udp", saddr: "", cnet: "udp", caddr: "127.0.0.1"}, {snet: "udp", saddr: "0.0.0.0", cnet: "udp", caddr: "127.0.0.1"}, @@ -301,12 +301,12 @@ var datagramPacketConnServerTests = []struct { {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true, empty: true}, {snet: "udp", saddr: "[::1]", cnet: "udp", caddr: "[::1]", ipv6: true, dial: true, empty: true}, - {snet: "unixgram", saddr: tempfile("gotest5.net"), cnet: "unixgram", caddr: tempfile("gotest5.net.local")}, - {snet: "unixgram", saddr: tempfile("gotest5.net"), cnet: "unixgram", caddr: tempfile("gotest5.net.local"), dial: true}, - {snet: "unixgram", saddr: tempfile("gotest5.net"), cnet: "unixgram", caddr: tempfile("gotest5.net.local"), empty: true}, - {snet: "unixgram", saddr: tempfile("gotest5.net"), cnet: "unixgram", caddr: tempfile("gotest5.net.local"), dial: true, empty: true}, + {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr()}, + {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), dial: true}, + {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), empty: true}, + {snet: "unixgram", saddr: testUnixAddr(), cnet: "unixgram", caddr: testUnixAddr(), dial: true, empty: true}, - {snet: "unixgram", saddr: "@gotest6/net", cnet: "unixgram", caddr: "@gotest6/net.local", linux: true}, + {snet: "unixgram", saddr: "@gotest6/net", cnet: "unixgram", caddr: "@gotest6/net.local", linuxOnly: true}, } func TestDatagramPacketConnServer(t *testing.T) { @@ -315,7 +315,7 @@ func TestDatagramPacketConnServer(t *testing.T) { } for _, tt := range datagramPacketConnServerTests { - if skipServerTest(tt.snet, "unixgram", tt.saddr, tt.ipv6, tt.ipv4map, tt.linux) { + if skipServerTest(tt.snet, "unixgram", tt.saddr, tt.ipv6, tt.ipv4map, tt.linuxOnly) { continue } diff --git a/libgo/go/net/smtp/smtp.go b/libgo/go/net/smtp/smtp.go index a0a478a8524..87dea442c46 100644 --- a/libgo/go/net/smtp/smtp.go +++ b/libgo/go/net/smtp/smtp.go @@ -264,6 +264,8 @@ func (c *Client) Data() (io.WriteCloser, error) { return &dataCloser{c, c.Text.DotWriter()}, nil } +var testHookStartTLS func(*tls.Config) // nil, except for tests + // SendMail connects to the server at addr, switches to TLS if // possible, authenticates with the optional mechanism a if possible, // and then sends an email from address from, to addresses to, with @@ -278,7 +280,11 @@ func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { return err } if ok, _ := c.Extension("STARTTLS"); ok { - if err = c.StartTLS(nil); err != nil { + config := &tls.Config{ServerName: c.serverName} + if testHookStartTLS != nil { + testHookStartTLS(config) + } + if err = c.StartTLS(config); err != nil { return err } } diff --git a/libgo/go/net/smtp/smtp_test.go b/libgo/go/net/smtp/smtp_test.go index 2133dc7c7ba..3fba1ea5ae3 100644 --- a/libgo/go/net/smtp/smtp_test.go +++ b/libgo/go/net/smtp/smtp_test.go @@ -7,6 +7,8 @@ package smtp import ( "bufio" "bytes" + "crypto/tls" + "crypto/x509" "io" "net" "net/textproto" @@ -548,3 +550,145 @@ AUTH PLAIN AHVzZXIAcGFzcw== * QUIT ` + +func TestTLSClient(t *testing.T) { + ln := newLocalListener(t) + defer ln.Close() + errc := make(chan error) + go func() { + errc <- sendMail(ln.Addr().String()) + }() + conn, err := ln.Accept() + if err != nil { + t.Fatalf("failed to accept connection: %v", err) + } + defer conn.Close() + if err := serverHandle(conn, t); err != nil { + t.Fatalf("failed to handle connection: %v", err) + } + if err := <-errc; err != nil { + t.Fatalf("client error: %v", err) + } +} + +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 smtpSender struct { + w io.Writer +} + +func (s smtpSender) send(f string) { + s.w.Write([]byte(f + "\r\n")) +} + +// smtp server, finely tailored to deal with our own client only! +func serverHandle(c net.Conn, t *testing.T) error { + send := smtpSender{c}.send + send("220 127.0.0.1 ESMTP service ready") + s := bufio.NewScanner(c) + for s.Scan() { + switch s.Text() { + case "EHLO localhost": + send("250-127.0.0.1 ESMTP offers a warm hug of welcome") + send("250-STARTTLS") + send("250 Ok") + case "STARTTLS": + send("220 Go ahead") + keypair, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + return err + } + config := &tls.Config{Certificates: []tls.Certificate{keypair}} + c = tls.Server(c, config) + defer c.Close() + return serverHandleTLS(c, t) + default: + t.Fatalf("unrecognized command: %q", s.Text()) + } + } + return s.Err() +} + +func serverHandleTLS(c net.Conn, t *testing.T) error { + send := smtpSender{c}.send + s := bufio.NewScanner(c) + for s.Scan() { + switch s.Text() { + case "EHLO localhost": + send("250 Ok") + case "MAIL FROM:<joe1@example.com>": + send("250 Ok") + case "RCPT TO:<joe2@example.com>": + send("250 Ok") + case "DATA": + send("354 send the mail data, end with .") + send("250 Ok") + case "Subject: test": + case "": + case "howdy!": + case ".": + case "QUIT": + send("221 127.0.0.1 Service closing transmission channel") + return nil + default: + t.Fatalf("unrecognized command during TLS: %q", s.Text()) + } + } + return s.Err() +} + +func init() { + testRootCAs := x509.NewCertPool() + testRootCAs.AppendCertsFromPEM(localhostCert) + testHookStartTLS = func(config *tls.Config) { + config.RootCAs = testRootCAs + } +} + +func sendMail(hostPort string) error { + host, _, err := net.SplitHostPort(hostPort) + if err != nil { + return err + } + auth := PlainAuth("", "", "", host) + from := "joe1@example.com" + to := []string{"joe2@example.com"} + return SendMail(hostPort, auth, from, to, []byte("Subject: test\n\nhowdy!")) +} + +// (copied from net/http/httptest) +// localhostCert is a PEM-encoded TLS cert with SAN IPs +// "127.0.0.1" and "[::1]", expiring at the last second of 2049 (the end +// of ASN.1 time). +// generated from src/pkg/crypto/tls: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`) diff --git a/libgo/go/net/sock_bsd.go b/libgo/go/net/sock_bsd.go index 6c37109f5e4..48fb7852757 100644 --- a/libgo/go/net/sock_bsd.go +++ b/libgo/go/net/sock_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly freebsd nacl netbsd openbsd package net diff --git a/libgo/go/net/sock_cloexec.go b/libgo/go/net/sock_cloexec.go index 3f22cd8f570..dec81855b68 100644 --- a/libgo/go/net/sock_cloexec.go +++ b/libgo/go/net/sock_cloexec.go @@ -5,7 +5,7 @@ // This file implements sysSocket and accept for platforms that // provide a fast path for setting SetNonblock and CloseOnExec. -// +build linux +// +build freebsd linux package net @@ -13,18 +13,20 @@ import "syscall" // Wrapper around the socket system call that marks the returned file // descriptor as nonblocking and close-on-exec. -func sysSocket(f, t, p int) (int, error) { - s, err := syscall.Socket(f, t|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, p) - // The SOCK_NONBLOCK and SOCK_CLOEXEC flags were introduced in - // Linux 2.6.27. If we get an EINVAL error, fall back to - // using socket without them. - if err == nil || err != syscall.EINVAL { +func sysSocket(family, sotype, proto int) (int, error) { + s, err := syscall.Socket(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto) + // On Linux the SOCK_NONBLOCK and SOCK_CLOEXEC flags were + // introduced in 2.6.27 kernel and on FreeBSD both flags were + // introduced in 10 kernel. If we get an EINVAL error on Linux + // or EPROTONOSUPPORT error on FreeBSD, fall back to using + // socket without them. + if err == nil || (err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL) { return s, err } // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() - s, err = syscall.Socket(f, t, p) + s, err = syscall.Socket(family, sotype, proto) if err == nil { syscall.CloseOnExec(s) } @@ -41,12 +43,19 @@ func sysSocket(f, t, p int) (int, error) { // Wrapper around the accept system call that marks the returned file // descriptor as nonblocking and close-on-exec. -func accept(fd int) (int, syscall.Sockaddr, error) { - nfd, sa, err := syscall.Accept4(fd, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC) - // The accept4 system call was introduced in Linux 2.6.28. If - // we get an ENOSYS or EINVAL error, fall back to using accept. - if err == nil || (err != syscall.ENOSYS && err != syscall.EINVAL) { - return nfd, sa, err +func accept(s int) (int, syscall.Sockaddr, error) { + ns, sa, err := syscall.Accept4(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC) + // On Linux the accept4 system call was introduced in 2.6.28 + // kernel and on FreeBSD it was introduced in 10 kernel. If we + // get an ENOSYS error on both Linux and FreeBSD, or EINVAL + // error on Linux, fall back to using accept. + switch err { + default: // nil and errors other than the ones listed + return ns, sa, err + case syscall.ENOSYS: // syscall missing + case syscall.EINVAL: // some Linux use this instead of ENOSYS + case syscall.EACCES: // some Linux use this instead of ENOSYS + case syscall.EFAULT: // some Linux use this instead of ENOSYS } // See ../syscall/exec_unix.go for description of ForkLock. @@ -54,16 +63,16 @@ func accept(fd int) (int, syscall.Sockaddr, error) { // because we have put fd.sysfd into non-blocking mode. // However, a call to the File method will put it back into // blocking mode. We can't take that risk, so no use of ForkLock here. - nfd, sa, err = syscall.Accept(fd) + ns, sa, err = syscall.Accept(s) if err == nil { - syscall.CloseOnExec(nfd) + syscall.CloseOnExec(ns) } if err != nil { return -1, nil, err } - if err = syscall.SetNonblock(nfd, true); err != nil { - syscall.Close(nfd) + if err = syscall.SetNonblock(ns, true); err != nil { + syscall.Close(ns) return -1, nil, err } - return nfd, sa, nil + return ns, sa, nil } diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go index c2d343c5858..a6ef874c9fd 100644 --- a/libgo/go/net/sock_posix.go +++ b/libgo/go/net/sock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sock_solaris.go b/libgo/go/net/sock_solaris.go index 484e1fe461a..90fe9de894c 100644 --- a/libgo/go/net/sock_solaris.go +++ b/libgo/go/net/sock_solaris.go @@ -1,18 +1,13 @@ -// Copyright 2012 The Go Authors. All rights reserved. +// Copyright 2009 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. -// +build solaris - -// Sockets for Solaris - package net -import ( - "syscall" -) +import "syscall" func maxListenerBacklog() int { - // The kernel does not track the limit. + // TODO: Implement this + // NOTE: Never return a number bigger than 1<<16 - 1. See issue 5030. return syscall.SOMAXCONN } diff --git a/libgo/go/net/sockopt_bsd.go b/libgo/go/net/sockopt_bsd.go index 4b9c2f9afbe..2fa3b6f1d36 100644 --- a/libgo/go/net/sockopt_bsd.go +++ b/libgo/go/net/sockopt_bsd.go @@ -2,16 +2,29 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly freebsd nacl netbsd openbsd package net import ( "os" + "runtime" "syscall" ) func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { + if runtime.GOOS == "dragonfly" && sotype != syscall.SOCK_RAW { + // On DragonFly BSD, we adjust the ephemeral port + // range because unlike other BSD systems its default + // port range doesn't conform to IANA recommendation + // as described in RFC 6355 and is pretty narrow. + switch family { + case syscall.AF_INET: + syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_PORTRANGE, syscall.IP_PORTRANGE_HIGH) + case syscall.AF_INET6: + syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_PORTRANGE, syscall.IPV6_PORTRANGE_HIGH) + } + } if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { // Allow both IP versions even if the OS default // is otherwise. Note that some operating systems diff --git a/libgo/go/net/sockopt_posix.go b/libgo/go/net/sockopt_posix.go index ff3bc689940..921918c37f5 100644 --- a/libgo/go/net/sockopt_posix.go +++ b/libgo/go/net/sockopt_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net diff --git a/libgo/go/net/sockopt_solaris.go b/libgo/go/net/sockopt_solaris.go new file mode 100644 index 00000000000..54c20b1409b --- /dev/null +++ b/libgo/go/net/sockopt_solaris.go @@ -0,0 +1,32 @@ +// 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. + +package net + +import ( + "os" + "syscall" +) + +func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { + if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { + // Allow both IP versions even if the OS default + // is otherwise. Note that some operating systems + // never admit this option. + syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only)) + } + // Allow broadcast. + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)) +} + +func setDefaultListenerSockopts(s int) error { + // Allow reuse of recently-used addresses. + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)) +} + +func setDefaultMulticastSockopts(s int) error { + // Allow multicast UDP and raw IP datagram sockets to listen + // concurrently across multiple listeners. + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)) +} diff --git a/libgo/go/net/sockoptip_bsd.go b/libgo/go/net/sockoptip_bsd.go index 2199e480d42..87132f0f461 100644 --- a/libgo/go/net/sockoptip_bsd.go +++ b/libgo/go/net/sockoptip_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly freebsd nacl netbsd openbsd package net diff --git a/libgo/go/net/sockoptip_posix.go b/libgo/go/net/sockoptip_posix.go index c2579be9114..b5c80e44909 100644 --- a/libgo/go/net/sockoptip_posix.go +++ b/libgo/go/net/sockoptip_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd windows package net diff --git a/libgo/go/net/sockoptip_stub.go b/libgo/go/net/sockoptip_stub.go new file mode 100644 index 00000000000..dcd3a22b57d --- /dev/null +++ b/libgo/go/net/sockoptip_stub.go @@ -0,0 +1,39 @@ +// 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. + +// +build solaris + +package net + +import "syscall" + +func setIPv4MulticastInterface(fd *netFD, ifi *Interface) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} + +func setIPv4MulticastLoopback(fd *netFD, v bool) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} + +func joinIPv4Group(fd *netFD, ifi *Interface, ip IP) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} + +func setIPv6MulticastInterface(fd *netFD, ifi *Interface) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} + +func setIPv6MulticastLoopback(fd *netFD, v bool) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} + +func joinIPv6Group(fd *netFD, ifi *Interface, ip IP) error { + // See golang.org/issue/7399. + return syscall.EINVAL +} diff --git a/libgo/go/net/sys_cloexec.go b/libgo/go/net/sys_cloexec.go index bbfcc1a4fc4..898fb7c0c2c 100644 --- a/libgo/go/net/sys_cloexec.go +++ b/libgo/go/net/sys_cloexec.go @@ -5,7 +5,7 @@ // This file implements sysSocket and accept for platforms that do not // provide a fast path for setting SetNonblock and CloseOnExec. -// +build darwin dragonfly freebsd netbsd openbsd +// +build darwin dragonfly nacl netbsd openbsd solaris package net @@ -13,10 +13,10 @@ import "syscall" // Wrapper around the socket system call that marks the returned file // descriptor as nonblocking and close-on-exec. -func sysSocket(f, t, p int) (int, error) { +func sysSocket(family, sotype, proto int) (int, error) { // See ../syscall/exec_unix.go for description of ForkLock. syscall.ForkLock.RLock() - s, err := syscall.Socket(f, t, p) + s, err := syscall.Socket(family, sotype, proto) if err == nil { syscall.CloseOnExec(s) } @@ -33,22 +33,22 @@ func sysSocket(f, t, p int) (int, error) { // Wrapper around the accept system call that marks the returned file // descriptor as nonblocking and close-on-exec. -func accept(fd int) (int, syscall.Sockaddr, error) { +func accept(s int) (int, syscall.Sockaddr, error) { // See ../syscall/exec_unix.go for description of ForkLock. // It is probably okay to hold the lock across syscall.Accept // because we have put fd.sysfd into non-blocking mode. // However, a call to the File method will put it back into // blocking mode. We can't take that risk, so no use of ForkLock here. - nfd, sa, err := syscall.Accept(fd) + ns, sa, err := syscall.Accept(s) if err == nil { - syscall.CloseOnExec(nfd) + syscall.CloseOnExec(ns) } if err != nil { return -1, nil, err } - if err = syscall.SetNonblock(nfd, true); err != nil { - syscall.Close(nfd) + if err = syscall.SetNonblock(ns, true); err != nil { + syscall.Close(ns) return -1, nil, err } - return nfd, sa, nil + return ns, sa, nil } diff --git a/libgo/go/net/tcp_test.go b/libgo/go/net/tcp_test.go index 62fd99f5c0b..c04198ea000 100644 --- a/libgo/go/net/tcp_test.go +++ b/libgo/go/net/tcp_test.go @@ -97,6 +97,7 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { b.Fatalf("Listen failed: %v", err) } defer ln.Close() + serverSem := make(chan bool, numConcurrent) // Acceptor. go func() { for { @@ -104,9 +105,13 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { if err != nil { break } + serverSem <- true // Server connection. go func(c Conn) { - defer c.Close() + defer func() { + c.Close() + <-serverSem + }() if timeout { c.SetDeadline(time.Now().Add(time.Hour)) // Not intended to fire. } @@ -119,13 +124,13 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { }(c) } }() - sem := make(chan bool, numConcurrent) + clientSem := make(chan bool, numConcurrent) for i := 0; i < conns; i++ { - sem <- true + clientSem <- true // Client connection. go func() { defer func() { - <-sem + <-clientSem }() c, err := Dial("tcp", ln.Addr().String()) if err != nil { @@ -144,8 +149,9 @@ func benchmarkTCP(b *testing.B, persistent, timeout bool, laddr string) { } }() } - for i := 0; i < cap(sem); i++ { - sem <- true + for i := 0; i < numConcurrent; i++ { + clientSem <- true + serverSem <- true } } @@ -185,7 +191,8 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for p := 0; p < P; p++ { s, err := ln.Accept() if err != nil { - b.Fatalf("Accept failed: %v", err) + b.Errorf("Accept failed: %v", err) + return } servers[p] = s } @@ -217,7 +224,8 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { buf[0] = v _, err := c.Write(buf[:]) if err != nil { - b.Fatalf("Write failed: %v", err) + b.Errorf("Write failed: %v", err) + return } } }(clients[p]) @@ -232,7 +240,8 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for i := 0; i < N; i++ { _, err := s.Read(buf[:]) if err != nil { - b.Fatalf("Read failed: %v", err) + b.Errorf("Read failed: %v", err) + return } pipe <- buf[0] } @@ -250,7 +259,8 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { buf[0] = v _, err := s.Write(buf[:]) if err != nil { - b.Fatalf("Write failed: %v", err) + b.Errorf("Write failed: %v", err) + return } } s.Close() @@ -263,7 +273,8 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { for i := 0; i < N; i++ { _, err := c.Read(buf[:]) if err != nil { - b.Fatalf("Read failed: %v", err) + b.Errorf("Read failed: %v", err) + return } } c.Close() @@ -388,7 +399,7 @@ func TestIPv6LinkLocalUnicastTCP(t *testing.T) { {"tcp6", "[" + laddr + "%" + ifi.Name + "]:0", false}, } switch runtime.GOOS { - case "darwin", "freebsd", "opensbd", "netbsd": + case "darwin", "freebsd", "openbsd", "netbsd": tests = append(tests, []test{ {"tcp", "[localhost%" + ifi.Name + "]:0", true}, {"tcp6", "[localhost%" + ifi.Name + "]:0", true}, @@ -460,15 +471,25 @@ func TestTCPConcurrentAccept(t *testing.T) { wg.Done() }() } - for i := 0; i < 10*N; i++ { - c, err := Dial("tcp", ln.Addr().String()) + attempts := 10 * N + fails := 0 + d := &Dialer{Timeout: 200 * time.Millisecond} + for i := 0; i < attempts; i++ { + c, err := d.Dial("tcp", ln.Addr().String()) if err != nil { - t.Fatalf("Dial failed: %v", err) + fails++ + } else { + c.Close() } - c.Close() } ln.Close() wg.Wait() + if fails > attempts/9 { // see issues 7400 and 7541 + t.Fatalf("too many Dial failed: %v", fails) + } + if fails > 0 { + t.Logf("# of failed Dials: %v", fails) + } } func TestTCPReadWriteMallocs(t *testing.T) { diff --git a/libgo/go/net/tcpsock_plan9.go b/libgo/go/net/tcpsock_plan9.go index 6e1a8b9a192..52019d7b4eb 100644 --- a/libgo/go/net/tcpsock_plan9.go +++ b/libgo/go/net/tcpsock_plan9.go @@ -32,7 +32,7 @@ func (c *TCPConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseRead() + return c.fd.closeRead() } // CloseWrite shuts down the writing side of the TCP connection. @@ -41,20 +41,21 @@ func (c *TCPConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseWrite() + return c.fd.closeWrite() } -// SetLinger sets the behavior of Close() on a connection which still +// SetLinger sets the behavior of Close on a connection which still // has data waiting to be sent or to be acknowledged. // -// If sec < 0 (the default), Close returns immediately and the -// operating system finishes sending the data in the background. +// If sec < 0 (the default), the operating system finishes sending the +// data in the background. // -// If sec == 0, Close returns immediately and the operating system -// discards any unsent or unacknowledged data. +// If sec == 0, the operating system discards any unsent or +// unacknowledged data. // -// If sec > 0, Close blocks for at most sec seconds waiting for data -// to be sent and acknowledged. +// If sec > 0, the data is sent in the background as with sec < 0. On +// some operating systems after sec seconds have elapsed any remaining +// unsent data may be discarded. func (c *TCPConn) SetLinger(sec int) error { return syscall.EPLAN9 } diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go index 00c692e4233..b79b115ca5b 100644 --- a/libgo/go/net/tcpsock_posix.go +++ b/libgo/go/net/tcpsock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net @@ -78,7 +78,7 @@ func (c *TCPConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseRead() + return c.fd.closeRead() } // CloseWrite shuts down the writing side of the TCP connection. @@ -87,20 +87,21 @@ func (c *TCPConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseWrite() + return c.fd.closeWrite() } -// SetLinger sets the behavior of Close() on a connection which still +// SetLinger sets the behavior of Close on a connection which still // has data waiting to be sent or to be acknowledged. // -// If sec < 0 (the default), Close returns immediately and the -// operating system finishes sending the data in the background. +// If sec < 0 (the default), the operating system finishes sending the +// data in the background. // -// If sec == 0, Close returns immediately and the operating system -// discards any unsent or unacknowledged data. +// If sec == 0, the operating system discards any unsent or +// unacknowledged data. // -// If sec > 0, Close blocks for at most sec seconds waiting for data -// to be sent and acknowledged. +// If sec > 0, the data is sent in the background as with sec < 0. On +// some operating systems after sec seconds have elapsed any remaining +// unsent data may be discarded. func (c *TCPConn) SetLinger(sec int) error { if !c.ok() { return syscall.EINVAL diff --git a/libgo/go/net/tcpsockopt_dragonfly.go b/libgo/go/net/tcpsockopt_dragonfly.go new file mode 100644 index 00000000000..d10a77773d8 --- /dev/null +++ b/libgo/go/net/tcpsockopt_dragonfly.go @@ -0,0 +1,29 @@ +// Copyright 2009 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. + +package net + +import ( + "os" + "syscall" + "time" +) + +// Set keep alive period. +func setKeepAlivePeriod(fd *netFD, d time.Duration) error { + if err := fd.incref(); err != nil { + return err + } + defer fd.decref() + + // The kernel expects milliseconds so round to next highest millisecond. + d += (time.Millisecond - time.Nanosecond) + msecs := int(time.Duration(d.Nanoseconds()) / time.Millisecond) + + err := os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, msecs)) + if err != nil { + return err + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, msecs)) +} diff --git a/libgo/go/net/tcpsockopt_posix.go b/libgo/go/net/tcpsockopt_posix.go index e03476ac634..6484bad4b45 100644 --- a/libgo/go/net/tcpsockopt_posix.go +++ b/libgo/go/net/tcpsockopt_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net diff --git a/libgo/go/net/tcpsockopt_solaris.go b/libgo/go/net/tcpsockopt_solaris.go new file mode 100644 index 00000000000..eaab6b6787b --- /dev/null +++ b/libgo/go/net/tcpsockopt_solaris.go @@ -0,0 +1,27 @@ +// Copyright 2013 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. + +// TCP socket options for solaris + +package net + +import ( + "os" + "syscall" + "time" +) + +// Set keep alive period. +func setKeepAlivePeriod(fd *netFD, d time.Duration) error { + if err := fd.incref(); err != nil { + return err + } + defer fd.decref() + + // The kernel expects seconds so round to next highest second. + d += (time.Second - time.Nanosecond) + secs := int(d.Seconds()) + + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd.sysfd, syscall.IPPROTO_TCP, syscall.SO_KEEPALIVE, secs)) +} diff --git a/libgo/go/net/tcpsockopt_unix.go b/libgo/go/net/tcpsockopt_unix.go index 89d9143b52e..2693a541d20 100644 --- a/libgo/go/net/tcpsockopt_unix.go +++ b/libgo/go/net/tcpsockopt_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build dragonfly freebsd linux netbsd +// +build freebsd linux nacl netbsd package net diff --git a/libgo/go/net/textproto/reader.go b/libgo/go/net/textproto/reader.go index b0c07413c19..eea9207f252 100644 --- a/libgo/go/net/textproto/reader.go +++ b/libgo/go/net/textproto/reader.go @@ -562,19 +562,12 @@ const toLower = 'a' - 'A' // allowed to mutate the provided byte slice before returning the // string. func canonicalMIMEHeaderKey(a []byte) string { - // Look for it in commonHeaders , so that we can avoid an - // allocation by sharing the strings among all users - // of textproto. If we don't find it, a has been canonicalized - // so just return string(a). upper := true - lo := 0 - hi := len(commonHeaders) - for i := 0; i < len(a); i++ { + for i, c := range a { // Canonicalize: first letter upper case // and upper case after each dash. // (Host, User-Agent, If-Modified-Since). // MIME headers are ASCII only, so no Unicode issues. - c := a[i] if c == ' ' { c = '-' } else if upper && 'a' <= c && c <= 'z' { @@ -584,60 +577,61 @@ func canonicalMIMEHeaderKey(a []byte) string { } a[i] = c upper = c == '-' // for next time - - if lo < hi { - for lo < hi && (len(commonHeaders[lo]) <= i || commonHeaders[lo][i] < c) { - lo++ - } - for hi > lo && commonHeaders[hi-1][i] > c { - hi-- - } - } } - if lo < hi && len(commonHeaders[lo]) == len(a) { - return commonHeaders[lo] + // The compiler recognizes m[string(byteSlice)] as a special + // case, so a copy of a's bytes into a new string does not + // happen in this map lookup: + if v := commonHeader[string(a)]; v != "" { + return v } return string(a) } -var commonHeaders = []string{ - "Accept", - "Accept-Charset", - "Accept-Encoding", - "Accept-Language", - "Accept-Ranges", - "Cache-Control", - "Cc", - "Connection", - "Content-Id", - "Content-Language", - "Content-Length", - "Content-Transfer-Encoding", - "Content-Type", - "Cookie", - "Date", - "Dkim-Signature", - "Etag", - "Expires", - "From", - "Host", - "If-Modified-Since", - "If-None-Match", - "In-Reply-To", - "Last-Modified", - "Location", - "Message-Id", - "Mime-Version", - "Pragma", - "Received", - "Return-Path", - "Server", - "Set-Cookie", - "Subject", - "To", - "User-Agent", - "Via", - "X-Forwarded-For", - "X-Imforwards", - "X-Powered-By", +// commonHeader interns common header strings. +var commonHeader = make(map[string]string) + +func init() { + for _, v := range []string{ + "Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Accept-Ranges", + "Cache-Control", + "Cc", + "Connection", + "Content-Id", + "Content-Language", + "Content-Length", + "Content-Transfer-Encoding", + "Content-Type", + "Cookie", + "Date", + "Dkim-Signature", + "Etag", + "Expires", + "From", + "Host", + "If-Modified-Since", + "If-None-Match", + "In-Reply-To", + "Last-Modified", + "Location", + "Message-Id", + "Mime-Version", + "Pragma", + "Received", + "Return-Path", + "Server", + "Set-Cookie", + "Subject", + "To", + "User-Agent", + "Via", + "X-Forwarded-For", + "X-Imforwards", + "X-Powered-By", + } { + commonHeader[v] = v + } } diff --git a/libgo/go/net/textproto/reader_test.go b/libgo/go/net/textproto/reader_test.go index cc12912b634..c89566635e1 100644 --- a/libgo/go/net/textproto/reader_test.go +++ b/libgo/go/net/textproto/reader_test.go @@ -247,24 +247,21 @@ func TestRFC959Lines(t *testing.T) { } func TestCommonHeaders(t *testing.T) { - // need to disable the commonHeaders-based optimization - // during this check, or we'd not be testing anything - oldch := commonHeaders - commonHeaders = []string{} - defer func() { commonHeaders = oldch }() - - last := "" - for _, h := range oldch { - if last > h { - t.Errorf("%v is out of order", h) - } - if last == h { - t.Errorf("%v is duplicated", h) + for h := range commonHeader { + if h != CanonicalMIMEHeaderKey(h) { + t.Errorf("Non-canonical header %q in commonHeader", h) } - if canon := CanonicalMIMEHeaderKey(h); h != canon { - t.Errorf("%v is not canonical", h) + } + t.Skip("gccgo escape analysis") + b := []byte("content-Length") + want := "Content-Length" + n := testing.AllocsPerRun(200, func() { + if x := canonicalMIMEHeaderKey(b); x != want { + t.Fatalf("canonicalMIMEHeaderKey(%q) = %q; want %q", b, x, want) } - last = h + }) + if n > 0 { + t.Errorf("canonicalMIMEHeaderKey allocs = %v; want 0", n) } } diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go index 35d427a69c0..9ef0c4d15cc 100644 --- a/libgo/go/net/timeout_test.go +++ b/libgo/go/net/timeout_test.go @@ -120,6 +120,9 @@ func TestReadTimeout(t *testing.T) { t.Fatalf("Read: expected err %v, got %v", errClosing, err) } default: + if err == io.EOF && runtime.GOOS == "nacl" { // close enough; golang.org/issue/8044 + break + } if err != errClosing { t.Fatalf("Read: expected err %v, got %v", errClosing, err) } @@ -348,7 +351,8 @@ func TestReadWriteDeadline(t *testing.T) { go func() { c, err := ln.Accept() if err != nil { - t.Fatalf("Accept: %v", err) + t.Errorf("Accept: %v", err) + return } defer c.Close() lnquit <- true @@ -493,10 +497,7 @@ func testVariousDeadlines(t *testing.T, maxProcs int) { clientc <- copyRes{n, err, d} }() - tooLong := 2 * time.Second - if runtime.GOOS == "windows" { - tooLong = 5 * time.Second - } + tooLong := 5 * time.Second select { case res := <-clientc: if isTimeout(res.err) { @@ -536,7 +537,8 @@ func TestReadDeadlineDataAvailable(t *testing.T) { go func() { c, err := ln.Accept() if err != nil { - t.Fatalf("Accept: %v", err) + t.Errorf("Accept: %v", err) + return } defer c.Close() n, err := c.Write([]byte(msg)) @@ -574,7 +576,8 @@ func TestWriteDeadlineBufferAvailable(t *testing.T) { go func() { c, err := ln.Accept() if err != nil { - t.Fatalf("Accept: %v", err) + t.Errorf("Accept: %v", err) + return } defer c.Close() c.SetWriteDeadline(time.Now().Add(-5 * time.Second)) // in the past @@ -610,7 +613,8 @@ func TestAcceptDeadlineConnectionAvailable(t *testing.T) { go func() { c, err := Dial("tcp", ln.Addr().String()) if err != nil { - t.Fatalf("Dial: %v", err) + t.Errorf("Dial: %v", err) + return } defer c.Close() var buf [1]byte @@ -669,7 +673,8 @@ func TestProlongTimeout(t *testing.T) { s, err := ln.Accept() connected <- true if err != nil { - t.Fatalf("ln.Accept: %v", err) + t.Errorf("ln.Accept: %v", err) + return } defer s.Close() s.SetDeadline(time.Now().Add(time.Hour)) @@ -706,7 +711,7 @@ func TestProlongTimeout(t *testing.T) { func TestDeadlineRace(t *testing.T) { switch runtime.GOOS { - case "plan9": + case "nacl", "plan9": t.Skipf("skipping test on %q", runtime.GOOS) } diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go index 6f4d2152c3c..e1778779cf5 100644 --- a/libgo/go/net/udp_test.go +++ b/libgo/go/net/udp_test.go @@ -201,6 +201,10 @@ func TestIPv6LinkLocalUnicastUDP(t *testing.T) { {"udp", "[" + laddr + "%" + ifi.Name + "]:0", false}, {"udp6", "[" + laddr + "%" + ifi.Name + "]:0", false}, } + // The first udp test fails on DragonFly - see issue 7473. + if runtime.GOOS == "dragonfly" { + tests = tests[1:] + } switch runtime.GOOS { case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd": tests = append(tests, []test{ diff --git a/libgo/go/net/udpsock.go b/libgo/go/net/udpsock.go index 0dd0dbd7114..4c99ae4af68 100644 --- a/libgo/go/net/udpsock.go +++ b/libgo/go/net/udpsock.go @@ -4,10 +4,6 @@ package net -import "errors" - -var ErrWriteToConnected = errors.New("use of WriteTo with pre-connected UDP") - // UDPAddr represents the address of a UDP end point. type UDPAddr struct { IP IP diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go index 142da8186f1..5dfba94e9a6 100644 --- a/libgo/go/net/udpsock_posix.go +++ b/libgo/go/net/udpsock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net @@ -64,7 +64,7 @@ func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { if !c.ok() { return 0, nil, syscall.EINVAL } - n, sa, err := c.fd.ReadFrom(b) + n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port} @@ -93,7 +93,7 @@ func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, return 0, 0, 0, nil, syscall.EINVAL } var sa syscall.Sockaddr - n, oobn, flags, sa, err = c.fd.ReadMsg(b, oob) + n, oobn, flags, sa, err = c.fd.readMsg(b, oob) switch sa := sa.(type) { case *syscall.SockaddrInet4: addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port} @@ -124,7 +124,7 @@ func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { if err != nil { return 0, &OpError{"write", c.fd.net, addr, err} } - return c.fd.WriteTo(b, sa) + return c.fd.writeTo(b, sa) } // WriteTo implements the PacketConn WriteTo method. @@ -156,7 +156,7 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er if err != nil { return 0, 0, &OpError{"write", c.fd.net, addr, err} } - return c.fd.WriteMsg(b, oob, sa) + return c.fd.writeMsg(b, oob, sa) } // DialUDP connects to the remote address raddr on the network net, diff --git a/libgo/go/net/unix_test.go b/libgo/go/net/unix_test.go index 91df3ff8876..05643ddf9ae 100644 --- a/libgo/go/net/unix_test.go +++ b/libgo/go/net/unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !plan9,!windows +// +build !nacl,!plan9,!windows package net @@ -151,6 +151,73 @@ func TestUnixAutobindClose(t *testing.T) { ln.Close() } +func TestUnixgramWrite(t *testing.T) { + addr := testUnixAddr() + laddr, err := ResolveUnixAddr("unixgram", addr) + if err != nil { + t.Fatalf("ResolveUnixAddr failed: %v", err) + } + c, err := ListenPacket("unixgram", addr) + if err != nil { + t.Fatalf("ListenPacket failed: %v", err) + } + defer os.Remove(addr) + defer c.Close() + + testUnixgramWriteConn(t, laddr) + testUnixgramWritePacketConn(t, laddr) +} + +func testUnixgramWriteConn(t *testing.T, raddr *UnixAddr) { + c, err := Dial("unixgram", raddr.String()) + if err != nil { + t.Fatalf("Dial failed: %v", err) + } + defer c.Close() + + if _, err := c.(*UnixConn).WriteToUnix([]byte("Connection-oriented mode socket"), raddr); err == nil { + t.Fatal("WriteToUnix should fail") + } else if err.(*OpError).Err != ErrWriteToConnected { + t.Fatalf("WriteToUnix should fail as ErrWriteToConnected: %v", err) + } + if _, err = c.(*UnixConn).WriteTo([]byte("Connection-oriented mode socket"), raddr); err == nil { + t.Fatal("WriteTo should fail") + } else if err.(*OpError).Err != ErrWriteToConnected { + t.Fatalf("WriteTo should fail as ErrWriteToConnected: %v", err) + } + if _, _, err = c.(*UnixConn).WriteMsgUnix([]byte("Connection-oriented mode socket"), nil, raddr); err == nil { + t.Fatal("WriteTo should fail") + } else if err.(*OpError).Err != ErrWriteToConnected { + t.Fatalf("WriteMsgUnix should fail as ErrWriteToConnected: %v", err) + } + if _, err := c.Write([]byte("Connection-oriented mode socket")); err != nil { + t.Fatalf("Write failed: %v", err) + } +} + +func testUnixgramWritePacketConn(t *testing.T, raddr *UnixAddr) { + addr := testUnixAddr() + c, err := ListenPacket("unixgram", addr) + if err != nil { + t.Fatalf("ListenPacket failed: %v", err) + } + defer os.Remove(addr) + defer c.Close() + + if _, err := c.(*UnixConn).WriteToUnix([]byte("Connectionless mode socket"), raddr); err != nil { + t.Fatalf("WriteToUnix failed: %v", err) + } + if _, err := c.WriteTo([]byte("Connectionless mode socket"), raddr); err != nil { + t.Fatalf("WriteTo failed: %v", err) + } + if _, _, err := c.(*UnixConn).WriteMsgUnix([]byte("Connectionless mode socket"), nil, raddr); err != nil { + t.Fatalf("WriteMsgUnix failed: %v", err) + } + if _, err := c.(*UnixConn).Write([]byte("Connectionless mode socket")); err == nil { + t.Fatal("Write should fail") + } +} + func TestUnixConnLocalAndRemoteNames(t *testing.T) { for _, laddr := range []string{"", testUnixAddr()} { laddr := laddr diff --git a/libgo/go/net/unixsock_posix.go b/libgo/go/net/unixsock_posix.go index 54d9d16c99e..2610779bfd2 100644 --- a/libgo/go/net/unixsock_posix.go +++ b/libgo/go/net/unixsock_posix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin dragonfly freebsd linux netbsd openbsd windows +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows package net @@ -124,7 +124,7 @@ func (c *UnixConn) ReadFromUnix(b []byte) (n int, addr *UnixAddr, err error) { if !c.ok() { return 0, nil, syscall.EINVAL } - n, sa, err := c.fd.ReadFrom(b) + n, sa, err := c.fd.readFrom(b) switch sa := sa.(type) { case *syscall.SockaddrUnix: if sa.Name != "" { @@ -151,7 +151,7 @@ func (c *UnixConn) ReadMsgUnix(b, oob []byte) (n, oobn, flags int, addr *UnixAdd if !c.ok() { return 0, 0, 0, nil, syscall.EINVAL } - n, oobn, flags, sa, err := c.fd.ReadMsg(b, oob) + n, oobn, flags, sa, err := c.fd.readMsg(b, oob) switch sa := sa.(type) { case *syscall.SockaddrUnix: if sa.Name != "" { @@ -171,6 +171,9 @@ func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) { if !c.ok() { return 0, syscall.EINVAL } + if c.fd.isConnected { + return 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + } if addr == nil { return 0, &OpError{Op: "write", Net: c.fd.net, Addr: nil, Err: errMissingAddress} } @@ -178,7 +181,7 @@ func (c *UnixConn) WriteToUnix(b []byte, addr *UnixAddr) (n int, err error) { return 0, syscall.EAFNOSUPPORT } sa := &syscall.SockaddrUnix{Name: addr.Name} - return c.fd.WriteTo(b, sa) + return c.fd.writeTo(b, sa) } // WriteTo implements the PacketConn WriteTo method. @@ -200,14 +203,17 @@ func (c *UnixConn) WriteMsgUnix(b, oob []byte, addr *UnixAddr) (n, oobn int, err if !c.ok() { return 0, 0, syscall.EINVAL } + if c.fd.sotype == syscall.SOCK_DGRAM && c.fd.isConnected { + return 0, 0, &OpError{Op: "write", Net: c.fd.net, Addr: addr, Err: ErrWriteToConnected} + } if addr != nil { if addr.Net != sotypeToNet(c.fd.sotype) { return 0, 0, syscall.EAFNOSUPPORT } sa := &syscall.SockaddrUnix{Name: addr.Name} - return c.fd.WriteMsg(b, oob, sa) + return c.fd.writeMsg(b, oob, sa) } - return c.fd.WriteMsg(b, oob, nil) + return c.fd.writeMsg(b, oob, nil) } // CloseRead shuts down the reading side of the Unix domain connection. @@ -216,7 +222,7 @@ func (c *UnixConn) CloseRead() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseRead() + return c.fd.closeRead() } // CloseWrite shuts down the writing side of the Unix domain connection. @@ -225,7 +231,7 @@ func (c *UnixConn) CloseWrite() error { if !c.ok() { return syscall.EINVAL } - return c.fd.CloseWrite() + return c.fd.closeWrite() } // DialUnix connects to the remote address raddr on the network net, diff --git a/libgo/go/net/url/url.go b/libgo/go/net/url/url.go index 3b3787202b7..75f650a2756 100644 --- a/libgo/go/net/url/url.go +++ b/libgo/go/net/url/url.go @@ -502,7 +502,7 @@ func (v Values) Set(key, value string) { v[key] = []string{value} } -// Add adds the key to value. It appends to any existing +// Add adds the value to key. It appends to any existing // values associated with key. func (v Values) Add(key, value string) { v[key] = append(v[key], value) diff --git a/libgo/go/net/url/url_test.go b/libgo/go/net/url/url_test.go index 7578eb15b90..cad758f2385 100644 --- a/libgo/go/net/url/url_test.go +++ b/libgo/go/net/url/url_test.go @@ -251,6 +251,17 @@ var urltests = []URLTest{ }, "file:///home/adg/rabbits", }, + // "Windows" paths are no exception to the rule. + // See golang.org/issue/6027, especially comment #9. + { + "file:///C:/FooBar/Baz.txt", + &URL{ + Scheme: "file", + Host: "", + Path: "/C:/FooBar/Baz.txt", + }, + "file:///C:/FooBar/Baz.txt", + }, // case-insensitive scheme { "MaIlTo:webmaster@golang.org", |