summaryrefslogtreecommitdiff
path: root/libgo/go/net
diff options
context:
space:
mode:
authorian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2013-01-29 20:52:43 +0000
committerian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2013-01-29 20:52:43 +0000
commit12ebd6294172cc1108bbadab78fea03e890a6da4 (patch)
tree4f2fad1f4b778519bdd5941185c7e1d032af055b /libgo/go/net
parent6effa4dc115122a3a4838de0a302dfcadcefeeca (diff)
downloadgcc-12ebd6294172cc1108bbadab78fea03e890a6da4.tar.gz
libgo: Update Go library to master revision 15489/921e53d4863c.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@195560 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/net')
-rw-r--r--libgo/go/net/dial.go19
-rw-r--r--libgo/go/net/dial_test.go9
-rw-r--r--libgo/go/net/dialgoogle_test.go12
-rw-r--r--libgo/go/net/fd_bsd.go (renamed from libgo/go/net/fd_netbsd.go)2
-rw-r--r--libgo/go/net/fd_openbsd.go116
-rw-r--r--libgo/go/net/fd_plan9.go6
-rw-r--r--libgo/go/net/fd_unix.go19
-rw-r--r--libgo/go/net/fd_windows.go64
-rw-r--r--libgo/go/net/file_test.go6
-rw-r--r--libgo/go/net/file_unix.go4
-rw-r--r--libgo/go/net/http/cgi/child.go12
-rw-r--r--libgo/go/net/http/cgi/child_test.go24
-rw-r--r--libgo/go/net/http/cgi/host_test.go80
-rw-r--r--libgo/go/net/http/fs_test.go18
-rw-r--r--libgo/go/net/http/header.go10
-rw-r--r--libgo/go/net/http/proxy_test.go2
-rw-r--r--libgo/go/net/http/request.go46
-rw-r--r--libgo/go/net/http/request_test.go59
-rw-r--r--libgo/go/net/http/response_test.go105
-rw-r--r--libgo/go/net/http/responsewrite_test.go138
-rw-r--r--libgo/go/net/http/serve_test.go37
-rw-r--r--libgo/go/net/http/server.go400
-rw-r--r--libgo/go/net/http/transfer.go15
-rw-r--r--libgo/go/net/http/transport.go10
-rw-r--r--libgo/go/net/http/transport_test.go63
-rw-r--r--libgo/go/net/interface_bsd.go6
-rw-r--r--libgo/go/net/interface_test.go16
-rw-r--r--libgo/go/net/ip.go8
-rw-r--r--libgo/go/net/ipraw_test.go6
-rw-r--r--libgo/go/net/lookup_test.go18
-rw-r--r--libgo/go/net/multicast_posix_test.go12
-rw-r--r--libgo/go/net/net_test.go9
-rw-r--r--libgo/go/net/parse_test.go3
-rw-r--r--libgo/go/net/protoconn_test.go12
-rw-r--r--libgo/go/net/server_test.go3
-rw-r--r--libgo/go/net/sock_cloexec.go69
-rw-r--r--libgo/go/net/sock_posix.go7
-rw-r--r--libgo/go/net/sock_windows.go11
-rw-r--r--libgo/go/net/sys_cloexec.go54
-rw-r--r--libgo/go/net/tcp_test.go3
-rw-r--r--libgo/go/net/tcpsock_posix.go5
-rw-r--r--libgo/go/net/timeout_test.go63
-rw-r--r--libgo/go/net/udp_test.go6
-rw-r--r--libgo/go/net/udpsock_posix.go49
-rw-r--r--libgo/go/net/unicast_posix_test.go25
-rw-r--r--libgo/go/net/url/url.go31
-rw-r--r--libgo/go/net/url/url_test.go37
47 files changed, 1025 insertions, 704 deletions
diff --git a/libgo/go/net/dial.go b/libgo/go/net/dial.go
index c1eb983cc0f..354028a157a 100644
--- a/libgo/go/net/dial.go
+++ b/libgo/go/net/dial.go
@@ -5,7 +5,6 @@
package net
import (
- "runtime"
"time"
)
@@ -113,30 +112,16 @@ func dialAddr(net, addr string, addri Addr, deadline time.Time) (c Conn, err err
return
}
-const useDialTimeoutRace = runtime.GOOS == "windows" || runtime.GOOS == "plan9"
-
// DialTimeout acts like Dial but takes a timeout.
// The timeout includes name resolution, if required.
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
- if useDialTimeoutRace {
- // On windows and plan9, use the relatively inefficient
- // goroutine-racing implementation of DialTimeout that
- // doesn't push down deadlines to the pollster.
- // TODO: remove this once those are implemented.
- return dialTimeoutRace(net, addr, timeout)
- }
- deadline := time.Now().Add(timeout)
- _, addri, err := resolveNetAddr("dial", net, addr, deadline)
- if err != nil {
- return nil, err
- }
- return dialAddr(net, addr, addri, deadline)
+ return dialTimeout(net, addr, timeout)
}
// dialTimeoutRace is the old implementation of DialTimeout, still used
// on operating systems where the deadline hasn't been pushed down
// into the pollserver.
-// TODO: fix this on Windows and plan9.
+// TODO: fix this on plan9.
func dialTimeoutRace(net, addr string, timeout time.Duration) (Conn, error) {
t := time.NewTimer(timeout)
defer t.Stop()
diff --git a/libgo/go/net/dial_test.go b/libgo/go/net/dial_test.go
index f30dee30164..aa53b667ded 100644
--- a/libgo/go/net/dial_test.go
+++ b/libgo/go/net/dial_test.go
@@ -74,8 +74,7 @@ func TestDialTimeout(t *testing.T) {
// by default. FreeBSD likely works, but is untested.
// TODO(rsc):
// The timeout never happens on Windows. Why? Issue 3016.
- t.Logf("skipping test on %q; untested.", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q; untested.", runtime.GOOS)
}
connected := 0
@@ -107,8 +106,7 @@ func TestDialTimeout(t *testing.T) {
func TestSelfConnect(t *testing.T) {
if runtime.GOOS == "windows" {
// TODO(brainman): do not know why it hangs.
- t.Logf("skipping known-broken test on windows")
- return
+ t.Skip("skipping known-broken test on windows")
}
// Test that Dial does not honor self-connects.
// See the comment in DialTCP.
@@ -228,8 +226,7 @@ func TestDialError(t *testing.T) {
func TestDialTimeoutFDLeak(t *testing.T) {
if runtime.GOOS != "linux" {
// TODO(bradfitz): test on other platforms
- t.Logf("skipping test on %s", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln := newLocalListener(t)
diff --git a/libgo/go/net/dialgoogle_test.go b/libgo/go/net/dialgoogle_test.go
index dd3c4ba7e15..73a94f5bf1c 100644
--- a/libgo/go/net/dialgoogle_test.go
+++ b/libgo/go/net/dialgoogle_test.go
@@ -56,8 +56,7 @@ var googleaddrsipv4 = []string{
func TestDialGoogleIPv4(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
// Insert an actual IPv4 address for google.com
@@ -112,17 +111,14 @@ var googleaddrsipv6 = []string{
func TestDialGoogleIPv6(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
// Only run tcp6 if the kernel will take it.
if !supportsIPv6 {
- t.Logf("skipping test; ipv6 is not supported")
- return
+ t.Skip("skipping test; ipv6 is not supported")
}
if !*testIPv6 {
- t.Logf("test disabled; use -ipv6 to enable")
- return
+ t.Skip("test disabled; use -ipv6 to enable")
}
// Insert an actual IPv6 address for ipv6.google.com
diff --git a/libgo/go/net/fd_netbsd.go b/libgo/go/net/fd_bsd.go
index 35d84c30ef6..4f5dd6e524a 100644
--- a/libgo/go/net/fd_netbsd.go
+++ b/libgo/go/net/fd_bsd.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build freebsd netbsd openbsd
+
// Waiting for FDs via kqueue/kevent.
package net
diff --git a/libgo/go/net/fd_openbsd.go b/libgo/go/net/fd_openbsd.go
deleted file mode 100644
index 35d84c30ef6..00000000000
--- a/libgo/go/net/fd_openbsd.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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.
-
-// Waiting for FDs via kqueue/kevent.
-
-package net
-
-import (
- "os"
- "syscall"
-)
-
-type pollster struct {
- kq int
- eventbuf [10]syscall.Kevent_t
- events []syscall.Kevent_t
-
- // An event buffer for AddFD/DelFD.
- // Must hold pollServer lock.
- kbuf [1]syscall.Kevent_t
-}
-
-func newpollster() (p *pollster, err error) {
- p = new(pollster)
- if p.kq, err = syscall.Kqueue(); err != nil {
- return nil, os.NewSyscallError("kqueue", err)
- }
- syscall.CloseOnExec(p.kq)
- p.events = p.eventbuf[0:0]
- return p, nil
-}
-
-func (p *pollster) AddFD(fd int, mode int, repeat bool) (bool, error) {
- // pollServer is locked.
-
- var kmode int
- if mode == 'r' {
- kmode = syscall.EVFILT_READ
- } else {
- kmode = syscall.EVFILT_WRITE
- }
- ev := &p.kbuf[0]
- // EV_ADD - add event to kqueue list
- // EV_ONESHOT - delete the event the first time it triggers
- flags := syscall.EV_ADD
- if !repeat {
- flags |= syscall.EV_ONESHOT
- }
- syscall.SetKevent(ev, fd, kmode, flags)
-
- n, err := syscall.Kevent(p.kq, p.kbuf[:], nil, nil)
- if err != nil {
- return false, os.NewSyscallError("kevent", err)
- }
- if n != 1 || (ev.Flags&syscall.EV_ERROR) == 0 || int(ev.Ident) != fd || int(ev.Filter) != kmode {
- return false, os.NewSyscallError("kqueue phase error", err)
- }
- if ev.Data != 0 {
- return false, syscall.Errno(int(ev.Data))
- }
- return false, nil
-}
-
-func (p *pollster) DelFD(fd int, mode int) {
- // pollServer is locked.
-
- var kmode int
- if mode == 'r' {
- kmode = syscall.EVFILT_READ
- } else {
- kmode = syscall.EVFILT_WRITE
- }
- ev := &p.kbuf[0]
- // EV_DELETE - delete event from kqueue list
- syscall.SetKevent(ev, fd, kmode, syscall.EV_DELETE)
- syscall.Kevent(p.kq, p.kbuf[:], nil, nil)
-}
-
-func (p *pollster) WaitFD(s *pollServer, nsec int64) (fd int, mode int, err error) {
- var t *syscall.Timespec
- for len(p.events) == 0 {
- if nsec > 0 {
- if t == nil {
- t = new(syscall.Timespec)
- }
- *t = syscall.NsecToTimespec(nsec)
- }
-
- s.Unlock()
- n, err := syscall.Kevent(p.kq, nil, p.eventbuf[:], t)
- s.Lock()
-
- if err != nil {
- if err == syscall.EINTR {
- continue
- }
- return -1, 0, os.NewSyscallError("kevent", err)
- }
- if n == 0 {
- return -1, 0, nil
- }
- p.events = p.eventbuf[:n]
- }
- ev := &p.events[0]
- p.events = p.events[1:]
- fd = int(ev.Ident)
- if ev.Filter == syscall.EVFILT_READ {
- mode = 'r'
- } else {
- mode = 'w'
- }
- return fd, mode, nil
-}
-
-func (p *pollster) Close() error { return os.NewSyscallError("close", syscall.Close(p.kq)) }
diff --git a/libgo/go/net/fd_plan9.go b/libgo/go/net/fd_plan9.go
index 6d7ab388ae7..3462792816e 100644
--- a/libgo/go/net/fd_plan9.go
+++ b/libgo/go/net/fd_plan9.go
@@ -23,6 +23,12 @@ var canCancelIO = true // used for testing current package
func sysInit() {
}
+func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
+ // On plan9, use the relatively inefficient
+ // goroutine-racing implementation.
+ return dialTimeoutRace(net, addr, timeout)
+}
+
func newFD(proto, name string, ctl *os.File, laddr, raddr Addr) *netFD {
return &netFD{proto, name, "/net/" + proto + "/" + name, ctl, nil, laddr, raddr}
}
diff --git a/libgo/go/net/fd_unix.go b/libgo/go/net/fd_unix.go
index 6d8af0ab2e2..e9d2e4165f1 100644
--- a/libgo/go/net/fd_unix.go
+++ b/libgo/go/net/fd_unix.go
@@ -288,10 +288,16 @@ func server(fd int) *pollServer {
return pollservers[k]
}
-func newFD(fd, family, sotype int, net string) (*netFD, error) {
- if err := syscall.SetNonblock(fd, true); err != nil {
+func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
+ deadline := time.Now().Add(timeout)
+ _, addri, err := resolveNetAddr("dial", net, addr, deadline)
+ if err != nil {
return nil, err
}
+ return dialAddr(net, addr, addri, deadline)
+}
+
+func newFD(fd, family, sotype int, net string) (*netFD, error) {
netfd := &netFD{
sysfd: fd,
family: family,
@@ -606,16 +612,11 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (netfd *netFD, err e
}
defer fd.decref()
- // See ../syscall/exec_unix.go for description of ForkLock.
- // It is okay to hold the lock across syscall.Accept
- // because we have put fd.sysfd into non-blocking mode.
var s int
var rsa syscall.Sockaddr
for {
- syscall.ForkLock.RLock()
- s, rsa, err = syscall.Accept(fd.sysfd)
+ s, rsa, err = accept(fd.sysfd)
if err != nil {
- syscall.ForkLock.RUnlock()
if err == syscall.EAGAIN {
if err = fd.pollServer.WaitRead(fd); err == nil {
continue
@@ -629,8 +630,6 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (netfd *netFD, err e
}
break
}
- syscall.CloseOnExec(s)
- syscall.ForkLock.RUnlock()
if netfd, err = newFD(s, fd.family, fd.sotype, fd.net); err != nil {
closesocket(s)
diff --git a/libgo/go/net/fd_windows.go b/libgo/go/net/fd_windows.go
index 18712191fee..ea6ef10ec1a 100644
--- a/libgo/go/net/fd_windows.go
+++ b/libgo/go/net/fd_windows.go
@@ -45,6 +45,28 @@ func closesocket(s syscall.Handle) error {
return syscall.Closesocket(s)
}
+func canUseConnectEx(net string) bool {
+ if net == "udp" || net == "udp4" || net == "udp6" {
+ // ConnectEx windows API does not support connectionless sockets.
+ return false
+ }
+ return syscall.LoadConnectEx() == nil
+}
+
+func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
+ if !canUseConnectEx(net) {
+ // Use the relatively inefficient goroutine-racing
+ // implementation of DialTimeout.
+ return dialTimeoutRace(net, addr, timeout)
+ }
+ deadline := time.Now().Add(timeout)
+ _, addri, err := resolveNetAddr("dial", net, addr, deadline)
+ if err != nil {
+ return nil, err
+ }
+ return dialAddr(net, addr, addri, deadline)
+}
+
// Interface for all IO operations.
type anOpIface interface {
Op() *anOp
@@ -321,8 +343,48 @@ func (fd *netFD) setAddr(laddr, raddr Addr) {
runtime.SetFinalizer(fd, (*netFD).closesocket)
}
+// Make new connection.
+
+type connectOp struct {
+ anOp
+ ra syscall.Sockaddr
+}
+
+func (o *connectOp) Submit() error {
+ return syscall.ConnectEx(o.fd.sysfd, o.ra, nil, 0, nil, &o.o)
+}
+
+func (o *connectOp) Name() string {
+ return "ConnectEx"
+}
+
func (fd *netFD) connect(ra syscall.Sockaddr) error {
- return syscall.Connect(fd.sysfd, ra)
+ if !canUseConnectEx(fd.net) {
+ return syscall.Connect(fd.sysfd, ra)
+ }
+ // ConnectEx windows API requires an unconnected, previously bound socket.
+ var la syscall.Sockaddr
+ switch ra.(type) {
+ case *syscall.SockaddrInet4:
+ la = &syscall.SockaddrInet4{}
+ case *syscall.SockaddrInet6:
+ la = &syscall.SockaddrInet6{}
+ default:
+ panic("unexpected type in connect")
+ }
+ if err := syscall.Bind(fd.sysfd, la); err != nil {
+ return err
+ }
+ // Call ConnectEx API.
+ var o connectOp
+ o.Init(fd, 'w')
+ o.ra = ra
+ _, err := iosrv.ExecIO(&o, fd.wdeadline.value())
+ if err != nil {
+ return err
+ }
+ // Refresh socket properties.
+ return syscall.Setsockopt(fd.sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.sysfd)), int32(unsafe.Sizeof(fd.sysfd)))
}
// Add a reference to this fd.
diff --git a/libgo/go/net/file_test.go b/libgo/go/net/file_test.go
index 95c0b66995e..78c62221dae 100644
--- a/libgo/go/net/file_test.go
+++ b/libgo/go/net/file_test.go
@@ -90,8 +90,7 @@ var fileListenerTests = []struct {
func TestFileListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
for _, tt := range fileListenerTests {
@@ -181,8 +180,7 @@ var filePacketConnTests = []struct {
func TestFilePacketConn(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
for _, tt := range filePacketConnTests {
diff --git a/libgo/go/net/file_unix.go b/libgo/go/net/file_unix.go
index 0a640801779..4c8403e4063 100644
--- a/libgo/go/net/file_unix.go
+++ b/libgo/go/net/file_unix.go
@@ -20,6 +20,10 @@ func newFileFD(f *os.File) (*netFD, error) {
}
syscall.CloseOnExec(fd)
syscall.ForkLock.RUnlock()
+ if err = syscall.SetNonblock(fd, true); err != nil {
+ closesocket(fd)
+ return nil, err
+ }
sotype, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TYPE)
if err != nil {
diff --git a/libgo/go/net/http/cgi/child.go b/libgo/go/net/http/cgi/child.go
index 1ba7bec5fc5..100b8b77760 100644
--- a/libgo/go/net/http/cgi/child.go
+++ b/libgo/go/net/http/cgi/child.go
@@ -91,10 +91,19 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
// TODO: cookies. parsing them isn't exported, though.
+ uriStr := params["REQUEST_URI"]
+ if uriStr == "" {
+ // Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
+ uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
+ s := params["QUERY_STRING"]
+ if s != "" {
+ uriStr += "?" + s
+ }
+ }
if r.Host != "" {
// Hostname is provided, so we can reasonably construct a URL,
// even if we have to assume 'http' for the scheme.
- rawurl := "http://" + r.Host + params["REQUEST_URI"]
+ rawurl := "http://" + r.Host + uriStr
url, err := url.Parse(rawurl)
if err != nil {
return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
@@ -104,7 +113,6 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
// Fallback logic if we don't have a Host header or the URL
// failed to parse
if r.URL == nil {
- uriStr := params["REQUEST_URI"]
url, err := url.Parse(uriStr)
if err != nil {
return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
diff --git a/libgo/go/net/http/cgi/child_test.go b/libgo/go/net/http/cgi/child_test.go
index ec53ab851ba..74e068014bb 100644
--- a/libgo/go/net/http/cgi/child_test.go
+++ b/libgo/go/net/http/cgi/child_test.go
@@ -82,6 +82,28 @@ func TestRequestWithoutHost(t *testing.T) {
t.Fatalf("unexpected nil URL")
}
if g, e := req.URL.String(), "/path?a=b"; e != g {
- t.Errorf("expected URL %q; got %q", e, g)
+ t.Errorf("URL = %q; want %q", g, e)
+ }
+}
+
+func TestRequestWithoutRequestURI(t *testing.T) {
+ env := map[string]string{
+ "SERVER_PROTOCOL": "HTTP/1.1",
+ "HTTP_HOST": "example.com",
+ "REQUEST_METHOD": "GET",
+ "SCRIPT_NAME": "/dir/scriptname",
+ "PATH_INFO": "/p1/p2",
+ "QUERY_STRING": "a=1&b=2",
+ "CONTENT_LENGTH": "123",
+ }
+ req, err := RequestFromMap(env)
+ if err != nil {
+ t.Fatalf("RequestFromMap: %v", err)
+ }
+ if req.URL == nil {
+ t.Fatalf("unexpected nil URL")
+ }
+ if g, e := req.URL.String(), "http://example.com/dir/scriptname/p1/p2?a=1&b=2"; e != g {
+ t.Errorf("URL = %q; want %q", g, e)
}
}
diff --git a/libgo/go/net/http/cgi/host_test.go b/libgo/go/net/http/cgi/host_test.go
index 85b52c9fdbc..cb6f1df1f45 100644
--- a/libgo/go/net/http/cgi/host_test.go
+++ b/libgo/go/net/http/cgi/host_test.go
@@ -63,17 +63,25 @@ readlines:
}
for key, expected := range expectedMap {
- if got := m[key]; got != expected {
+ got := m[key]
+ if key == "cwd" {
+ // For Windows. golang.org/issue/4645.
+ fi1, _ := os.Stat(got)
+ fi2, _ := os.Stat(expected)
+ if os.SameFile(fi1, fi2) {
+ got = expected
+ }
+ }
+ if got != expected {
t.Errorf("for key %q got %q; expected %q", key, got, expected)
}
}
return rw
}
-var cgiTested = false
-var cgiWorks bool
+var cgiTested, cgiWorks bool
-func skipTest(t *testing.T) bool {
+func check(t *testing.T) {
if !cgiTested {
cgiTested = true
cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil
@@ -81,16 +89,12 @@ func skipTest(t *testing.T) bool {
if !cgiWorks {
// No Perl on Windows, needed by test.cgi
// TODO: make the child process be Go, not Perl.
- t.Logf("Skipping test: test.cgi failed.")
- return true
+ t.Skip("Skipping test: test.cgi failed.")
}
- return false
}
func TestCGIBasicGet(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
Root: "/test.cgi",
@@ -124,9 +128,7 @@ func TestCGIBasicGet(t *testing.T) {
}
func TestCGIBasicGetAbsPath(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
pwd, err := os.Getwd()
if err != nil {
t.Fatalf("getwd error: %v", err)
@@ -144,9 +146,7 @@ func TestCGIBasicGetAbsPath(t *testing.T) {
}
func TestPathInfo(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
Root: "/test.cgi",
@@ -163,9 +163,7 @@ func TestPathInfo(t *testing.T) {
}
func TestPathInfoDirRoot(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
Root: "/myscript/",
@@ -181,9 +179,7 @@ func TestPathInfoDirRoot(t *testing.T) {
}
func TestDupHeaders(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
}
@@ -203,9 +199,7 @@ func TestDupHeaders(t *testing.T) {
}
func TestPathInfoNoRoot(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
Root: "",
@@ -221,9 +215,7 @@ func TestPathInfoNoRoot(t *testing.T) {
}
func TestCGIBasicPost(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
postReq := `POST /test.cgi?a=b HTTP/1.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
@@ -250,9 +242,7 @@ func chunk(s string) string {
// The CGI spec doesn't allow chunked requests.
func TestCGIPostChunked(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
postReq := `POST /test.cgi?a=b HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
@@ -273,9 +263,7 @@ Transfer-Encoding: chunked
}
func TestRedirect(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
h := &Handler{
Path: "testdata/test.cgi",
Root: "/test.cgi",
@@ -290,9 +278,7 @@ func TestRedirect(t *testing.T) {
}
func TestInternalRedirect(t *testing.T) {
- if skipTest(t) {
- return
- }
+ check(t)
baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
@@ -312,8 +298,9 @@ func TestInternalRedirect(t *testing.T) {
// TestCopyError tests that we kill the process if there's an error copying
// its output. (for example, from the client having gone away)
func TestCopyError(t *testing.T) {
- if skipTest(t) || runtime.GOOS == "windows" {
- return
+ check(t)
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
h := &Handler{
Path: "testdata/test.cgi",
@@ -376,10 +363,10 @@ func TestCopyError(t *testing.T) {
}
func TestDirUnix(t *testing.T) {
- if skipTest(t) || runtime.GOOS == "windows" {
- return
+ check(t)
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
-
cwd, _ := os.Getwd()
h := &Handler{
Path: "testdata/test.cgi",
@@ -406,8 +393,7 @@ func TestDirUnix(t *testing.T) {
func TestDirWindows(t *testing.T) {
if runtime.GOOS != "windows" {
- t.Logf("Skipping windows specific test.")
- return
+ t.Skip("Skipping windows specific test.")
}
cgifile, _ := filepath.Abs("testdata/test.cgi")
@@ -416,8 +402,7 @@ func TestDirWindows(t *testing.T) {
var err error
perl, err = exec.LookPath("perl")
if err != nil {
- t.Logf("Skipping test: perl not found.")
- return
+ t.Skip("Skipping test: perl not found.")
}
perl, _ = filepath.Abs(perl)
@@ -459,8 +444,7 @@ func TestEnvOverride(t *testing.T) {
var err error
perl, err = exec.LookPath("perl")
if err != nil {
- t.Logf("Skipping test: perl not found.")
- return
+ t.Skipf("Skipping test: perl not found.")
}
perl, _ = filepath.Abs(perl)
diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go
index 59e30a19fca..d42014c265b 100644
--- a/libgo/go/net/http/fs_test.go
+++ b/libgo/go/net/http/fs_test.go
@@ -257,8 +257,7 @@ func TestFileServerImplicitLeadingSlash(t *testing.T) {
func TestDirJoin(t *testing.T) {
wfi, err := os.Stat("/etc/hosts")
if err != nil {
- t.Logf("skipping test; no /etc/hosts file")
- return
+ t.Skip("skipping test; no /etc/hosts file")
}
test := func(d Dir, name string) {
f, err := d.Open(name)
@@ -665,13 +664,10 @@ func TestServeContent(t *testing.T) {
// verifies that sendfile is being used on Linux
func TestLinuxSendfile(t *testing.T) {
if runtime.GOOS != "linux" {
- t.Logf("skipping; linux-only test")
- return
+ t.Skip("skipping; linux-only test")
}
- _, err := exec.LookPath("strace")
- if err != nil {
- t.Logf("skipping; strace not found in path")
- return
+ if _, err := exec.LookPath("strace"); err != nil {
+ t.Skip("skipping; strace not found in path")
}
ln, err := net.Listen("tcp", "127.0.0.1:0")
@@ -690,10 +686,8 @@ func TestLinuxSendfile(t *testing.T) {
child.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...)
child.Stdout = &buf
child.Stderr = &buf
- err = child.Start()
- if err != nil {
- t.Logf("skipping; failed to start straced child: %v", err)
- return
+ if err := child.Start(); err != nil {
+ t.Skipf("skipping; failed to start straced child: %v", err)
}
res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go
index 91417366ae8..f479b7b4eb9 100644
--- a/libgo/go/net/http/header.go
+++ b/libgo/go/net/http/header.go
@@ -54,6 +54,16 @@ func (h Header) Write(w io.Writer) error {
return h.WriteSubset(w, nil)
}
+func (h Header) clone() Header {
+ h2 := make(Header, len(h))
+ for k, vv := range h {
+ vv2 := make([]string, len(vv))
+ copy(vv2, vv)
+ h2[k] = vv2
+ }
+ return h2
+}
+
var timeFormats = []string{
TimeFormat,
time.RFC850,
diff --git a/libgo/go/net/http/proxy_test.go b/libgo/go/net/http/proxy_test.go
index 86db976b838..449ccaeea76 100644
--- a/libgo/go/net/http/proxy_test.go
+++ b/libgo/go/net/http/proxy_test.go
@@ -31,7 +31,7 @@ var UseProxyTests = []struct {
{"localhost.net", true}, // not match as suffix of address
{"local.localhost", true}, // not match as prefix as address
{"barbarbaz.net", true}, // not match because NO_PROXY have a '.'
- {"www.foobar.com", true}, // not match because NO_PROXY is not .foobar.com
+ {"www.foobar.com", false}, // match because NO_PROXY includes "foobar.com"
}
func TestUseProxy(t *testing.T) {
diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go
index 0b6e6cbab58..217f35b4833 100644
--- a/libgo/go/net/http/request.go
+++ b/libgo/go/net/http/request.go
@@ -71,7 +71,13 @@ var reqWriteExcludeHeader = map[string]bool{
// or to be sent by a client.
type Request struct {
Method string // GET, POST, PUT, etc.
- URL *url.URL
+
+ // URL is created 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)
+ URL *url.URL
// The protocol version for incoming requests.
// Outgoing requests always use HTTP/1.1.
@@ -325,11 +331,20 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err
}
// TODO(bradfitz): escape at least newlines in ruri?
- bw := bufio.NewWriter(w)
- fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)
+ // Wrap the writer in a bufio Writer if it's not already buffered.
+ // Don't always call NewWriter, as that forces a bytes.Buffer
+ // and other small bufio Writers to have a minimum 4k buffer
+ // size.
+ var bw *bufio.Writer
+ if _, ok := w.(io.ByteWriter); !ok {
+ bw = bufio.NewWriter(w)
+ w = bw
+ }
+
+ fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)
// Header lines
- fmt.Fprintf(bw, "Host: %s\r\n", host)
+ fmt.Fprintf(w, "Host: %s\r\n", host)
// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
@@ -340,7 +355,7 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err
}
}
if userAgent != "" {
- fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent)
+ fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
}
// Process Body,ContentLength,Close,Trailer
@@ -348,33 +363,36 @@ func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) err
if err != nil {
return err
}
- err = tw.WriteHeader(bw)
+ err = tw.WriteHeader(w)
if err != nil {
return err
}
// TODO: split long values? (If so, should share code with Conn.Write)
- err = req.Header.WriteSubset(bw, reqWriteExcludeHeader)
+ err = req.Header.WriteSubset(w, reqWriteExcludeHeader)
if err != nil {
return err
}
if extraHeaders != nil {
- err = extraHeaders.Write(bw)
+ err = extraHeaders.Write(w)
if err != nil {
return err
}
}
- io.WriteString(bw, "\r\n")
+ io.WriteString(w, "\r\n")
// Write body and trailer
- err = tw.WriteBody(bw)
+ err = tw.WriteBody(w)
if err != nil {
return err
}
- return bw.Flush()
+ if bw != nil {
+ return bw.Flush()
+ }
+ return nil
}
// ParseHTTPVersion parses a HTTP version string.
@@ -427,10 +445,12 @@ func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
}
if body != nil {
switch v := body.(type) {
- case *strings.Reader:
- req.ContentLength = int64(v.Len())
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
+ case *bytes.Reader:
+ req.ContentLength = int64(v.Len())
+ case *strings.Reader:
+ req.ContentLength = int64(v.Len())
}
}
diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go
index 2f34d124128..bd757920b78 100644
--- a/libgo/go/net/http/request_test.go
+++ b/libgo/go/net/http/request_test.go
@@ -238,6 +238,65 @@ func TestNewRequestHost(t *testing.T) {
}
}
+func TestNewRequestContentLength(t *testing.T) {
+ readByte := func(r io.Reader) io.Reader {
+ var b [1]byte
+ r.Read(b[:])
+ return r
+ }
+ tests := []struct {
+ r io.Reader
+ want int64
+ }{
+ {bytes.NewReader([]byte("123")), 3},
+ {bytes.NewBuffer([]byte("1234")), 4},
+ {strings.NewReader("12345"), 5},
+ // Not detected:
+ {struct{ io.Reader }{strings.NewReader("xyz")}, 0},
+ {io.NewSectionReader(strings.NewReader("x"), 0, 6), 0},
+ {readByte(io.NewSectionReader(strings.NewReader("xy"), 0, 6)), 0},
+ }
+ for _, tt := range tests {
+ req, err := NewRequest("POST", "http://localhost/", tt.r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if req.ContentLength != tt.want {
+ t.Errorf("ContentLength(%#T) = %d; want %d", tt.r, req.ContentLength, tt.want)
+ }
+ }
+}
+
+type logWrites struct {
+ t *testing.T
+ dst *[]string
+}
+
+func (l logWrites) WriteByte(c byte) error {
+ l.t.Fatalf("unexpected WriteByte call")
+ return nil
+}
+
+func (l logWrites) Write(p []byte) (n int, err error) {
+ *l.dst = append(*l.dst, string(p))
+ return len(p), nil
+}
+
+func TestRequestWriteBufferedWriter(t *testing.T) {
+ got := []string{}
+ req, _ := NewRequest("GET", "http://foo.com/", nil)
+ req.Write(logWrites{t, &got})
+ want := []string{
+ "GET / HTTP/1.1\r\n",
+ "Host: foo.com\r\n",
+ "User-Agent: Go http package\r\n",
+ "\r\n",
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Writes = %q\n Want = %q", got, want)
+ }
+}
+
func testMissingFile(t *testing.T, req *Request) {
f, fh, err := req.FormFile("missing")
if f != nil {
diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go
index f31e5d09fe5..a00a4ae0a9b 100644
--- a/libgo/go/net/http/response_test.go
+++ b/libgo/go/net/http/response_test.go
@@ -124,7 +124,7 @@ var respTests = []respTest{
// Chunked response without Content-Length.
{
- "HTTP/1.0 200 OK\r\n" +
+ "HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"0a\r\n" +
@@ -137,12 +137,12 @@ var respTests = []respTest{
Response{
Status: "200 OK",
StatusCode: 200,
- Proto: "HTTP/1.0",
+ Proto: "HTTP/1.1",
ProtoMajor: 1,
- ProtoMinor: 0,
+ ProtoMinor: 1,
Request: dummyReq("GET"),
Header: Header{},
- Close: true,
+ Close: false,
ContentLength: -1,
TransferEncoding: []string{"chunked"},
},
@@ -152,7 +152,7 @@ var respTests = []respTest{
// Chunked response with Content-Length.
{
- "HTTP/1.0 200 OK\r\n" +
+ "HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Content-Length: 10\r\n" +
"\r\n" +
@@ -164,12 +164,12 @@ var respTests = []respTest{
Response{
Status: "200 OK",
StatusCode: 200,
- Proto: "HTTP/1.0",
+ Proto: "HTTP/1.1",
ProtoMajor: 1,
- ProtoMinor: 0,
+ ProtoMinor: 1,
Request: dummyReq("GET"),
Header: Header{},
- Close: true,
+ Close: false,
ContentLength: -1, // TODO(rsc): Fix?
TransferEncoding: []string{"chunked"},
},
@@ -177,23 +177,88 @@ var respTests = []respTest{
"Body here\n",
},
- // Chunked response in response to a HEAD request (the "chunked" should
- // be ignored, as HEAD responses never have bodies)
+ // Chunked response in response to a HEAD request
{
- "HTTP/1.0 200 OK\r\n" +
+ "HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n",
Response{
- Status: "200 OK",
- StatusCode: 200,
- Proto: "HTTP/1.0",
- ProtoMajor: 1,
- ProtoMinor: 0,
- Request: dummyReq("HEAD"),
- Header: Header{},
- Close: true,
- ContentLength: -1,
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("HEAD"),
+ Header: Header{},
+ TransferEncoding: []string{"chunked"},
+ Close: false,
+ ContentLength: -1,
+ },
+
+ "",
+ },
+
+ // Content-Length in response to a HEAD request
+ {
+ "HTTP/1.0 200 OK\r\n" +
+ "Content-Length: 256\r\n" +
+ "\r\n",
+
+ Response{
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.0",
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("HEAD"),
+ Header: Header{"Content-Length": {"256"}},
+ TransferEncoding: nil,
+ Close: true,
+ ContentLength: 256,
+ },
+
+ "",
+ },
+
+ // Content-Length in response to a HEAD request with HTTP/1.1
+ {
+ "HTTP/1.1 200 OK\r\n" +
+ "Content-Length: 256\r\n" +
+ "\r\n",
+
+ Response{
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.1",
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("HEAD"),
+ Header: Header{"Content-Length": {"256"}},
+ TransferEncoding: nil,
+ Close: false,
+ ContentLength: 256,
+ },
+
+ "",
+ },
+
+ // No Content-Length or Chunked in response to a HEAD request
+ {
+ "HTTP/1.0 200 OK\r\n" +
+ "\r\n",
+
+ Response{
+ Status: "200 OK",
+ StatusCode: 200,
+ Proto: "HTTP/1.0",
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("HEAD"),
+ Header: Header{},
+ TransferEncoding: nil,
+ Close: true,
+ ContentLength: -1,
},
"",
diff --git a/libgo/go/net/http/responsewrite_test.go b/libgo/go/net/http/responsewrite_test.go
index f8e63acf4f7..5c10e2161cf 100644
--- a/libgo/go/net/http/responsewrite_test.go
+++ b/libgo/go/net/http/responsewrite_test.go
@@ -15,83 +15,83 @@ type respWriteTest struct {
Raw string
}
-var respWriteTests = []respWriteTest{
- // HTTP/1.0, identity coding; no trailer
- {
- Response{
- StatusCode: 503,
- ProtoMajor: 1,
- ProtoMinor: 0,
- Request: dummyReq("GET"),
- Header: Header{},
- Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
- ContentLength: 6,
- },
+func TestResponseWrite(t *testing.T) {
+ respWriteTests := []respWriteTest{
+ // HTTP/1.0, identity coding; no trailer
+ {
+ Response{
+ StatusCode: 503,
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("GET"),
+ Header: Header{},
+ Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
+ ContentLength: 6,
+ },
- "HTTP/1.0 503 Service Unavailable\r\n" +
- "Content-Length: 6\r\n\r\n" +
- "abcdef",
- },
- // Unchunked response without Content-Length.
- {
- Response{
- StatusCode: 200,
- ProtoMajor: 1,
- ProtoMinor: 0,
- Request: dummyReq("GET"),
- Header: Header{},
- Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
- ContentLength: -1,
+ "HTTP/1.0 503 Service Unavailable\r\n" +
+ "Content-Length: 6\r\n\r\n" +
+ "abcdef",
},
- "HTTP/1.0 200 OK\r\n" +
- "\r\n" +
- "abcdef",
- },
- // HTTP/1.1, chunked coding; empty trailer; close
- {
- Response{
- StatusCode: 200,
- ProtoMajor: 1,
- ProtoMinor: 1,
- Request: dummyReq("GET"),
- Header: Header{},
- Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
- ContentLength: 6,
- TransferEncoding: []string{"chunked"},
- Close: true,
+ // Unchunked response without Content-Length.
+ {
+ Response{
+ StatusCode: 200,
+ ProtoMajor: 1,
+ ProtoMinor: 0,
+ Request: dummyReq("GET"),
+ Header: Header{},
+ Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
+ ContentLength: -1,
+ },
+ "HTTP/1.0 200 OK\r\n" +
+ "\r\n" +
+ "abcdef",
},
+ // HTTP/1.1, chunked coding; empty trailer; close
+ {
+ Response{
+ StatusCode: 200,
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("GET"),
+ Header: Header{},
+ Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
+ ContentLength: 6,
+ TransferEncoding: []string{"chunked"},
+ Close: true,
+ },
- "HTTP/1.1 200 OK\r\n" +
- "Connection: close\r\n" +
- "Transfer-Encoding: chunked\r\n\r\n" +
- "6\r\nabcdef\r\n0\r\n\r\n",
- },
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Transfer-Encoding: chunked\r\n\r\n" +
+ "6\r\nabcdef\r\n0\r\n\r\n",
+ },
- // Header value with a newline character (Issue 914).
- // Also tests removal of leading and trailing whitespace.
- {
- Response{
- StatusCode: 204,
- ProtoMajor: 1,
- ProtoMinor: 1,
- Request: dummyReq("GET"),
- Header: Header{
- "Foo": []string{" Bar\nBaz "},
+ // Header value with a newline character (Issue 914).
+ // Also tests removal of leading and trailing whitespace.
+ {
+ Response{
+ StatusCode: 204,
+ ProtoMajor: 1,
+ ProtoMinor: 1,
+ Request: dummyReq("GET"),
+ Header: Header{
+ "Foo": []string{" Bar\nBaz "},
+ },
+ Body: nil,
+ ContentLength: 0,
+ TransferEncoding: []string{"chunked"},
+ Close: true,
},
- Body: nil,
- ContentLength: 0,
- TransferEncoding: []string{"chunked"},
- Close: true,
- },
- "HTTP/1.1 204 No Content\r\n" +
- "Connection: close\r\n" +
- "Foo: Bar Baz\r\n" +
- "\r\n",
- },
-}
+ "HTTP/1.1 204 No Content\r\n" +
+ "Connection: close\r\n" +
+ "Foo: Bar Baz\r\n" +
+ "\r\n",
+ },
+ }
-func TestResponseWrite(t *testing.T) {
for i := range respWriteTests {
tt := &respWriteTests[i]
var braw bytes.Buffer
diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go
index 1de4171239d..886ed4e8f74 100644
--- a/libgo/go/net/http/serve_test.go
+++ b/libgo/go/net/http/serve_test.go
@@ -67,6 +67,7 @@ func (a dummyAddr) String() string {
type testConn struct {
readBuf bytes.Buffer
writeBuf bytes.Buffer
+ closec chan bool // if non-nil, send value to it on close
}
func (c *testConn) Read(b []byte) (int, error) {
@@ -78,6 +79,10 @@ func (c *testConn) Write(b []byte) (int, error) {
}
func (c *testConn) Close() error {
+ select {
+ case c.closec <- true:
+ default:
+ }
return nil
}
@@ -179,10 +184,11 @@ var vtests = []struct {
}
func TestHostHandlers(t *testing.T) {
+ mux := NewServeMux()
for _, h := range handlers {
- Handle(h.pattern, stringHandler(h.msg))
+ mux.Handle(h.pattern, stringHandler(h.msg))
}
- ts := httptest.NewServer(nil)
+ ts := httptest.NewServer(mux)
defer ts.Close()
conn, err := net.Dial("tcp", ts.Listener.Addr().String())
@@ -484,6 +490,7 @@ func TestChunkedResponseHeaders(t *testing.T) {
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted
+ w.(Flusher).Flush()
fmt.Fprintf(w, "I am a chunked response.")
}))
defer ts.Close()
@@ -764,6 +771,7 @@ func TestServerUnreadRequestBodyLittle(t *testing.T) {
t.Errorf("on request, read buffer length is %d; expected about 100 KB", conn.readBuf.Len())
}
rw.WriteHeader(200)
+ rw.(Flusher).Flush()
if g, e := conn.readBuf.Len(), 0; g != e {
t.Errorf("after WriteHeader, read buffer length is %d; want %d", g, e)
}
@@ -786,24 +794,24 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) {
"Content-Length: %d\r\n"+
"\r\n", len(body))))
conn.readBuf.Write([]byte(body))
-
- done := make(chan bool)
+ conn.closec = make(chan bool, 1)
ls := &oneConnListener{conn}
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
- defer close(done)
if conn.readBuf.Len() < len(body)/2 {
t.Errorf("on request, read buffer length is %d; expected about 1MB", conn.readBuf.Len())
}
rw.WriteHeader(200)
+ rw.(Flusher).Flush()
if conn.readBuf.Len() < len(body)/2 {
t.Errorf("post-WriteHeader, read buffer length is %d; expected about 1MB", conn.readBuf.Len())
}
- if c := rw.Header().Get("Connection"); c != "close" {
- t.Errorf(`Connection header = %q; want "close"`, c)
- }
}))
- <-done
+ <-conn.closec
+
+ if res := conn.writeBuf.String(); !strings.Contains(res, "Connection: close") {
+ t.Errorf("Expected a Connection: close header; got response: %s", res)
+ }
}
func TestTimeoutHandler(t *testing.T) {
@@ -1144,22 +1152,17 @@ func TestClientWriteShutdown(t *testing.T) {
// Tests that chunked server responses that write 1 byte at a time are
// buffered before chunk headers are added, not after chunk headers.
func TestServerBufferedChunking(t *testing.T) {
- if true {
- t.Logf("Skipping known broken test; see Issue 2357")
- return
- }
conn := new(testConn)
conn.readBuf.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
- done := make(chan bool)
+ conn.closec = make(chan bool, 1)
ls := &oneConnListener{conn}
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
- defer close(done)
- rw.Header().Set("Content-Type", "text/plain") // prevent sniffing, which buffers
+ rw.(Flusher).Flush() // force the Header to be sent, in chunking mode, not counting the length
rw.Write([]byte{'x'})
rw.Write([]byte{'y'})
rw.Write([]byte{'z'})
}))
- <-done
+ <-conn.closec
if !bytes.HasSuffix(conn.writeBuf.Bytes(), []byte("\r\n\r\n3\r\nxyz\r\n0\r\n\r\n")) {
t.Errorf("response didn't end with a single 3 byte 'xyz' chunk; got:\n%q",
conn.writeBuf.Bytes())
diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go
index 89a46f06bb2..434943d49a3 100644
--- a/libgo/go/net/http/server.go
+++ b/libgo/go/net/http/server.go
@@ -113,7 +113,6 @@ type conn struct {
lr *io.LimitedReader // io.LimitReader(sr)
buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc
tlsState *tls.ConnectionState // or nil when not using TLS
- body []byte
mu sync.Mutex // guards the following
clientGone bool // if client has disconnected mid-request
@@ -193,18 +192,85 @@ func (sr *switchReader) Read(p []byte) (n int, err error) {
return r.Read(p)
}
+// This should be >= 512 bytes for DetectContentType,
+// but otherwise it's somewhat arbitrary.
+const bufferBeforeChunkingSize = 2048
+
+// chunkWriter writes to a response's conn buffer, and is the writer
+// wrapped by the response.bufw buffered writer.
+//
+// chunkWriter also is responsible for finalizing the Header, including
+// conditionally setting the Content-Type and setting a Content-Length
+// in cases where the handler's final output is smaller than the buffer
+// size. It also conditionally adds chunk headers, when in chunking mode.
+//
+// See the comment above (*response).Write for the entire write flow.
+type chunkWriter struct {
+ res *response
+ header Header // a deep copy of r.Header, once WriteHeader is called
+ wroteHeader bool // whether the header's been sent
+
+ // set by the writeHeader method:
+ chunking bool // using chunked transfer encoding for reply body
+}
+
+var crlf = []byte("\r\n")
+
+func (cw *chunkWriter) Write(p []byte) (n int, err error) {
+ if !cw.wroteHeader {
+ cw.writeHeader(p)
+ }
+ if cw.chunking {
+ _, err = fmt.Fprintf(cw.res.conn.buf, "%x\r\n", len(p))
+ if err != nil {
+ return
+ }
+ }
+ n, err = cw.res.conn.buf.Write(p)
+ if cw.chunking && err == nil {
+ _, err = cw.res.conn.buf.Write(crlf)
+ }
+ return
+}
+
+func (cw *chunkWriter) flush() {
+ if !cw.wroteHeader {
+ cw.writeHeader(nil)
+ }
+ cw.res.conn.buf.Flush()
+}
+
+func (cw *chunkWriter) close() {
+ if !cw.wroteHeader {
+ cw.writeHeader(nil)
+ }
+ if cw.chunking {
+ // zero EOF chunk, trailer key/value pairs (currently
+ // unsupported in Go's server), followed by a blank
+ // line.
+ io.WriteString(cw.res.conn.buf, "0\r\n\r\n")
+ }
+}
+
// A response represents the server side of an HTTP response.
type response struct {
conn *conn
req *Request // request for this response
- chunking bool // using chunked transfer encoding for reply body
- wroteHeader bool // reply header has been written
+ wroteHeader bool // reply header has been (logically) written
wroteContinue bool // 100 Continue response was written
- header Header // reply header parameters
- written int64 // number of bytes written in body
- contentLength int64 // explicitly-declared Content-Length; or -1
- status int // status code passed to WriteHeader
- needSniff bool // need to sniff to find Content-Type
+
+ w *bufio.Writer // buffers output in chunks to chunkWriter
+ cw *chunkWriter
+
+ // handlerHeader is the Header that Handlers get access to,
+ // which may be retained and mutated even after WriteHeader.
+ // handlerHeader is copied into cw.header at WriteHeader
+ // time, and privately mutated thereafter.
+ handlerHeader Header
+
+ written int64 // number of bytes written in body
+ contentLength int64 // explicitly-declared Content-Length; or -1
+ status int // status code passed to WriteHeader
// close connection after this reply. set on request and
// updated after response from handler if there's a
@@ -220,6 +286,8 @@ type response struct {
// subsequent requests on this connection and stop reading
// input from it.
requestBodyLimitHit bool
+
+ handlerDone bool // set true when the handler exits
}
// requestTooLarge is called by maxBytesReader when too much input has
@@ -232,27 +300,46 @@ func (w *response) requestTooLarge() {
}
}
+// needsSniff returns whether a Content-Type still needs to be sniffed.
+func (w *response) needsSniff() bool {
+ return !w.cw.wroteHeader && w.handlerHeader.Get("Content-Type") == "" && w.written < sniffLen
+}
+
type writerOnly struct {
io.Writer
}
func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
- // Call WriteHeader before checking w.chunking if it hasn't
- // been called yet, since WriteHeader is what sets w.chunking.
if !w.wroteHeader {
w.WriteHeader(StatusOK)
}
- if !w.chunking && w.bodyAllowed() && !w.needSniff {
- w.Flush()
+
+ if w.needsSniff() {
+ n0, err := io.Copy(writerOnly{w}, io.LimitReader(src, sniffLen))
+ n += n0
+ if err != nil {
+ return n, err
+ }
+ }
+
+ w.w.Flush() // get rid of any previous writes
+ w.cw.flush() // make sure Header is written; flush data to rwc
+
+ // Now that cw has been flushed, its chunking field is guaranteed initialized.
+ if !w.cw.chunking && w.bodyAllowed() {
if rf, ok := w.conn.rwc.(io.ReaderFrom); ok {
- n, err = rf.ReadFrom(src)
- w.written += n
- return
+ n0, err := rf.ReadFrom(src)
+ n += n0
+ w.written += n0
+ return n, err
}
}
+
// Fall back to default io.Copy implementation.
// Use wrapper to hide w.ReadFrom from io.Copy.
- return io.Copy(writerOnly{w}, src)
+ n0, err := io.Copy(writerOnly{w}, src)
+ n += n0
+ return n, err
}
// noLimit is an effective infinite upper bound for io.LimitedReader
@@ -272,7 +359,6 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
c.rwc = newLoggingConn("server", c.rwc)
}
c.sr = switchReader{r: c.rwc}
- c.body = make([]byte, sniffLen)
c.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader)
br := bufio.NewReader(c.lr)
bw := bufio.NewWriter(c.rwc)
@@ -343,17 +429,20 @@ func (c *conn) readRequest() (w *response, err error) {
req.RemoteAddr = c.remoteAddr
req.TLS = c.tlsState
- w = new(response)
- w.conn = c
- w.req = req
- w.header = make(Header)
- w.contentLength = -1
- c.body = c.body[:0]
+ w = &response{
+ conn: c,
+ req: req,
+ handlerHeader: make(Header),
+ contentLength: -1,
+ cw: new(chunkWriter),
+ }
+ w.cw.res = w
+ w.w = bufio.NewWriterSize(w.cw, bufferBeforeChunkingSize)
return w, nil
}
func (w *response) Header() Header {
- return w.header
+ return w.handlerHeader
}
// maxPostHandlerReadBytes is the max number of Request.Body bytes not
@@ -379,30 +468,68 @@ func (w *response) WriteHeader(code int) {
w.wroteHeader = true
w.status = code
- // Check for a explicit (and valid) Content-Length header.
- var hasCL bool
- var contentLength int64
- if clenStr := w.header.get("Content-Length"); clenStr != "" {
- var err error
- contentLength, err = strconv.ParseInt(clenStr, 10, 64)
- if err == nil {
- hasCL = true
+ w.cw.header = w.handlerHeader.clone()
+
+ if cl := w.cw.header.get("Content-Length"); cl != "" {
+ v, err := strconv.ParseInt(cl, 10, 64)
+ if err == nil && v >= 0 {
+ w.contentLength = v
} else {
- log.Printf("http: invalid Content-Length of %q sent", clenStr)
- w.header.Del("Content-Length")
+ log.Printf("http: invalid Content-Length of %q", cl)
+ w.cw.header.Del("Content-Length")
+ }
+ }
+}
+
+// writeHeader finalizes the header sent to the client and writes it
+// to cw.res.conn.buf.
+//
+// p is not written by writeHeader, but is the first chunk of the body
+// that will be written. It is sniffed for a Content-Type if none is
+// set explicitly. It's also used to set the Content-Length, if the
+// total body size was small and the handler has already finished
+// running.
+func (cw *chunkWriter) writeHeader(p []byte) {
+ if cw.wroteHeader {
+ return
+ }
+ cw.wroteHeader = true
+
+ w := cw.res
+ code := w.status
+ done := w.handlerDone
+
+ // If the handler is done but never sent a Content-Length
+ // response header and this is our first (and last) write, set
+ // it, even to zero. This helps HTTP/1.0 clients keep their
+ // "keep-alive" connections alive.
+ if done && cw.header.get("Content-Length") == "" && w.req.Method != "HEAD" {
+ w.contentLength = int64(len(p))
+ cw.header.Set("Content-Length", strconv.Itoa(len(p)))
+ }
+
+ // 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() {
+ sentLength := cw.header.get("Content-Length") != ""
+ if sentLength && cw.header.get("Connection") == "keep-alive" {
+ w.closeAfterReply = false
}
}
+ // Check for a explicit (and valid) Content-Length header.
+ hasCL := w.contentLength != -1
+
if w.req.wantsHttp10KeepAlive() && (w.req.Method == "HEAD" || hasCL) {
- _, connectionHeaderSet := w.header["Connection"]
+ _, connectionHeaderSet := cw.header["Connection"]
if !connectionHeaderSet {
- w.header.Set("Connection", "keep-alive")
+ cw.header.Set("Connection", "keep-alive")
}
} else if !w.req.ProtoAtLeast(1, 1) || w.req.wantsClose() {
w.closeAfterReply = true
}
- if w.header.get("Connection") == "close" {
+ if cw.header.get("Connection") == "close" {
w.closeAfterReply = true
}
@@ -416,7 +543,7 @@ func (w *response) WriteHeader(code int) {
n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1)
if n >= maxPostHandlerReadBytes {
w.requestTooLarge()
- w.header.Set("Connection", "close")
+ cw.header.Set("Connection", "close")
} else {
w.req.Body.Close()
}
@@ -426,69 +553,65 @@ func (w *response) WriteHeader(code int) {
if code == StatusNotModified {
// Must not have body.
for _, header := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
- if w.header.get(header) != "" {
- // TODO: return an error if WriteHeader gets a return parameter
- // or set a flag on w to make future Writes() write an error page?
- // for now just log and drop the header.
- log.Printf("http: StatusNotModified response with header %q defined", header)
- w.header.Del(header)
+ // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers"
+ if cw.header.get(header) != "" {
+ cw.header.Del(header)
}
}
} else {
// If no content type, apply sniffing algorithm to body.
- if w.header.get("Content-Type") == "" && w.req.Method != "HEAD" {
- w.needSniff = true
+ if cw.header.get("Content-Type") == "" && w.req.Method != "HEAD" {
+ cw.header.Set("Content-Type", DetectContentType(p))
}
}
- if _, ok := w.header["Date"]; !ok {
- w.Header().Set("Date", time.Now().UTC().Format(TimeFormat))
+ if _, ok := cw.header["Date"]; !ok {
+ cw.header.Set("Date", time.Now().UTC().Format(TimeFormat))
}
- te := w.header.get("Transfer-Encoding")
+ te := cw.header.get("Transfer-Encoding")
hasTE := te != ""
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",
- te, contentLength)
- w.header.Del("Content-Length")
+ te, w.contentLength)
+ cw.header.Del("Content-Length")
hasCL = false
}
if w.req.Method == "HEAD" || code == StatusNotModified {
// do nothing
} else if code == StatusNoContent {
- w.header.Del("Transfer-Encoding")
+ cw.header.Del("Transfer-Encoding")
} else if hasCL {
- w.contentLength = contentLength
- w.header.Del("Transfer-Encoding")
+ cw.header.Del("Transfer-Encoding")
} else if w.req.ProtoAtLeast(1, 1) {
// HTTP/1.1 or greater: use chunked transfer encoding
// to avoid closing the connection at EOF.
// TODO: this blows away any custom or stacked Transfer-Encoding they
// might have set. Deal with that as need arises once we have a valid
// use case.
- w.chunking = true
- w.header.Set("Transfer-Encoding", "chunked")
+ cw.chunking = true
+ cw.header.Set("Transfer-Encoding", "chunked")
} else {
// HTTP version < 1.1: cannot do chunked transfer
// encoding and we don't know the Content-Length so
// signal EOF by closing connection.
w.closeAfterReply = true
- w.header.Del("Transfer-Encoding") // in case already set
+ cw.header.Del("Transfer-Encoding") // in case already set
}
// Cannot use Content-Length with non-identity Transfer-Encoding.
- if w.chunking {
- w.header.Del("Content-Length")
+ if cw.chunking {
+ cw.header.Del("Content-Length")
}
if !w.req.ProtoAtLeast(1, 0) {
return
}
- if w.closeAfterReply && !hasToken(w.header.get("Connection"), "close") {
- w.header.Set("Connection", "close")
+ if w.closeAfterReply && !hasToken(cw.header.get("Connection"), "close") {
+ cw.header.Set("Connection", "close")
}
proto := "HTTP/1.0"
@@ -501,37 +624,8 @@ func (w *response) WriteHeader(code int) {
text = "status code " + codestring
}
io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n")
- w.header.Write(w.conn.buf)
-
- // If we need to sniff the body, leave the header open.
- // Otherwise, end it here.
- if !w.needSniff {
- io.WriteString(w.conn.buf, "\r\n")
- }
-}
-
-// sniff uses the first block of written data,
-// stored in w.conn.body, to decide the Content-Type
-// for the HTTP body.
-func (w *response) sniff() {
- if !w.needSniff {
- return
- }
- w.needSniff = false
-
- data := w.conn.body
- fmt.Fprintf(w.conn.buf, "Content-Type: %s\r\n\r\n", DetectContentType(data))
-
- if len(data) == 0 {
- return
- }
- if w.chunking {
- fmt.Fprintf(w.conn.buf, "%x\r\n", len(data))
- }
- _, err := w.conn.buf.Write(data)
- if w.chunking && err == nil {
- io.WriteString(w.conn.buf, "\r\n")
- }
+ cw.header.Write(w.conn.buf)
+ w.conn.buf.Write(crlf)
}
// bodyAllowed returns true if a Write is allowed for this response type.
@@ -543,6 +637,38 @@ func (w *response) bodyAllowed() bool {
return w.status != StatusNotModified && w.req.Method != "HEAD"
}
+// The Life Of A Write is like this:
+//
+// Handler starts. No header has been sent. The handler can either
+// write a header, or just start writing. Writing before sending a header
+// sends an implicity empty 200 OK header.
+//
+// If the handler didn't declare a Content-Length up front, we either
+// go into chunking mode or, if the handler finishes running before
+// the chunking buffer size, we compute a Content-Length and send that
+// in the header instead.
+//
+// Likewise, if the handler didn't set a Content-Type, we sniff that
+// from the initial chunk of output.
+//
+// The Writers are wired together like:
+//
+// 1. *response (the ResponseWriter) ->
+// 2. (*response).w, a *bufio.Writer of bufferBeforeChunkingSize bytes
+// 3. chunkWriter.Writer (whose writeHeader finalizes Content-Length/Type)
+// and which writes the chunk headers, if needed.
+// 4. conn.buf, a bufio.Writer of default (4kB) bytes
+// 5. the rwc, the net.Conn.
+//
+// TODO(bradfitz): short-circuit some of the buffering when the
+// initial header contains both a Content-Type and Content-Length.
+// Also short-circuit in (1) when the header's been sent and not in
+// chunking mode, writing directly to (4) instead, if (2) has no
+// buffered data. More generally, we could short-circuit from (1) to
+// (3) even in chunking mode if the write size from (1) is over some
+// threshold and nothing is in (2). The answer might be mostly making
+// bufferBeforeChunkingSize smaller and having bufio's fast-paths deal
+// with this instead.
func (w *response) Write(data []byte) (n int, err error) {
if w.conn.hijacked() {
log.Print("http: response.Write on hijacked connection")
@@ -562,81 +688,20 @@ func (w *response) Write(data []byte) (n int, err error) {
if w.contentLength != -1 && w.written > w.contentLength {
return 0, ErrContentLength
}
-
- var m int
- if w.needSniff {
- // We need to sniff the beginning of the output to
- // determine the content type. Accumulate the
- // initial writes in w.conn.body.
- // Cap m so that append won't allocate.
- m = cap(w.conn.body) - len(w.conn.body)
- if m > len(data) {
- m = len(data)
- }
- w.conn.body = append(w.conn.body, data[:m]...)
- data = data[m:]
- if len(data) == 0 {
- // Copied everything into the buffer.
- // Wait for next write.
- return m, nil
- }
-
- // Filled the buffer; more data remains.
- // Sniff the content (flushes the buffer)
- // and then proceed with the remainder
- // of the data as a normal Write.
- // Calling sniff clears needSniff.
- w.sniff()
- }
-
- // TODO(rsc): if chunking happened after the buffering,
- // then there would be fewer chunk headers.
- // On the other hand, it would make hijacking more difficult.
- if w.chunking {
- fmt.Fprintf(w.conn.buf, "%x\r\n", len(data))
- }
- n, err = w.conn.buf.Write(data)
- if err == nil && w.chunking {
- if n != len(data) {
- err = io.ErrShortWrite
- }
- if err == nil {
- io.WriteString(w.conn.buf, "\r\n")
- }
- }
-
- return m + n, err
+ return w.w.Write(data)
}
func (w *response) finishRequest() {
- // If the handler never wrote any bytes and never sent a Content-Length
- // response header, set the length explicitly to zero. This helps
- // HTTP/1.0 clients keep their "keep-alive" connections alive, and for
- // HTTP/1.1 clients is just as good as the alternative: sending a
- // chunked response and immediately sending the zero-length EOF chunk.
- if w.written == 0 && w.header.get("Content-Length") == "" && w.req.Method != "HEAD" {
- w.header.Set("Content-Length", "0")
- }
- // 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() {
- sentLength := w.header.get("Content-Length") != ""
- if sentLength && w.header.get("Connection") == "keep-alive" {
- w.closeAfterReply = false
- }
- }
+ w.handlerDone = true
+
if !w.wroteHeader {
w.WriteHeader(StatusOK)
}
- if w.needSniff {
- w.sniff()
- }
- if w.chunking {
- io.WriteString(w.conn.buf, "0\r\n")
- // trailer key/value pairs, followed by blank line
- io.WriteString(w.conn.buf, "\r\n")
- }
+
+ w.w.Flush()
+ w.cw.close()
w.conn.buf.Flush()
+
// Close the body, unless we're about to close the whole TCP connection
// anyway.
if !w.closeAfterReply {
@@ -646,7 +711,7 @@ func (w *response) finishRequest() {
w.req.MultipartForm.RemoveAll()
}
- if w.contentLength != -1 && w.contentLength != w.written {
+ if w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written {
// Did not write enough. Avoid getting out of sync.
w.closeAfterReply = true
}
@@ -656,8 +721,8 @@ func (w *response) Flush() {
if !w.wroteHeader {
w.WriteHeader(StatusOK)
}
- w.sniff()
- w.conn.buf.Flush()
+ w.w.Flush()
+ w.cw.flush()
}
func (c *conn) finalFlush() {
@@ -809,6 +874,9 @@ func (w *response) sendExpectationFailed() {
// Hijack implements the Hijacker.Hijack method. Our response is both a ResponseWriter
// and a Hijacker.
func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
+ if w.wroteHeader {
+ w.cw.flush()
+ }
return w.conn.hijack()
}
@@ -1148,7 +1216,7 @@ func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
}
// Serve accepts incoming HTTP connections on the listener l,
-// creating a new service thread for each. The service threads
+// creating a new service goroutine for each. The service goroutines
// read requests and then call handler to reply to them.
// Handler is typically nil, in which case the DefaultServeMux is used.
func Serve(l net.Listener, handler Handler) error {
@@ -1182,7 +1250,7 @@ func (srv *Server) ListenAndServe() error {
}
// Serve accepts incoming connections on the Listener l, creating a
-// new service thread for each. The service threads read requests and
+// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
@@ -1327,7 +1395,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
// TimeoutHandler returns a Handler that runs h with the given time limit.
//
// The new Handler calls h.ServeHTTP to handle each request, but if a
-// call runs for more than ns nanoseconds, the handler responds with
+// call runs for longer than its time limit, the handler responds with
// a 503 Service Unavailable error and the given message in its body.
// (If msg is empty, a suitable default message will be sent.)
// After such a timeout, writes by h to its ResponseWriter will return
diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go
index 70ea15b8e4a..25b34addec7 100644
--- a/libgo/go/net/http/transfer.go
+++ b/libgo/go/net/http/transfer.go
@@ -87,10 +87,8 @@ func newTransferWriter(r interface{}) (t *transferWriter, err error) {
// Sanitize Body,ContentLength,TransferEncoding
if t.ResponseToHEAD {
t.Body = nil
- t.TransferEncoding = nil
- // ContentLength is expected to hold Content-Length
- if t.ContentLength < 0 {
- return nil, ErrMissingContentLength
+ if chunked(t.TransferEncoding) {
+ t.ContentLength = -1
}
} else {
if !atLeastHTTP11 || t.Body == nil {
@@ -122,9 +120,6 @@ func (t *transferWriter) shouldSendContentLength() bool {
if t.ContentLength > 0 {
return true
}
- if t.ResponseToHEAD {
- return true
- }
// Many servers expect a Content-Length for these methods
if t.Method == "POST" || t.Method == "PUT" {
return true
@@ -380,12 +375,6 @@ func fixTransferEncoding(requestMethod string, header Header) ([]string, error)
delete(header, "Transfer-Encoding")
- // Head responses have no bodies, so the transfer encoding
- // should be ignored.
- if requestMethod == "HEAD" {
- return nil, nil
- }
-
encodings := strings.Split(raw[0], ",")
te := make([]string, 0, len(encodings))
// TODO: Even though we only support "identity" and "chunked"
diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go
index d0505bf13f0..98e198e78af 100644
--- a/libgo/go/net/http/transport.go
+++ b/libgo/go/net/http/transport.go
@@ -450,7 +450,15 @@ func useProxy(addr string) bool {
if hasPort(p) {
p = p[:strings.LastIndex(p, ":")]
}
- if addr == p || (p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:])) {
+ if addr == p {
+ return false
+ }
+ if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
+ // no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
+ return false
+ }
+ if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
+ // no_proxy "foo.com" matches "bar.foo.com"
return false
}
}
diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go
index c37ef13a416..daaecae341b 100644
--- a/libgo/go/net/http/transport_test.go
+++ b/libgo/go/net/http/transport_test.go
@@ -390,8 +390,7 @@ func TestTransportServerClosingUnexpectedly(t *testing.T) {
// This fails pretty reliably with GOMAXPROCS=100 or something high.
func TestStressSurpriseServerCloses(t *testing.T) {
if testing.Short() {
- t.Logf("skipping test in short mode")
- return
+ t.Skip("skipping test in short mode")
}
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
w.Header().Set("Content-Length", "5")
@@ -1115,18 +1114,43 @@ func TestTransportNoHost(t *testing.T) {
}
}
-var proxyFromEnvTests = []struct {
+type proxyFromEnvTest struct {
+ req string // URL to fetch; blank means "http://example.com"
env string
- wanturl string
+ noenv string
+ want string
wanterr error
-}{
- {"127.0.0.1:8080", "http://127.0.0.1:8080", nil},
- {"cache.corp.example.com:1234", "http://cache.corp.example.com:1234", nil},
- {"cache.corp.example.com", "http://cache.corp.example.com", nil},
- {"https://cache.corp.example.com", "https://cache.corp.example.com", nil},
- {"http://127.0.0.1:8080", "http://127.0.0.1:8080", nil},
- {"https://127.0.0.1:8080", "https://127.0.0.1:8080", nil},
- {"", "<nil>", nil},
+}
+
+func (t proxyFromEnvTest) String() string {
+ var buf bytes.Buffer
+ if t.env != "" {
+ fmt.Fprintf(&buf, "http_proxy=%q", t.env)
+ }
+ if t.noenv != "" {
+ fmt.Fprintf(&buf, " no_proxy=%q", t.noenv)
+ }
+ req := "http://example.com"
+ if t.req != "" {
+ req = t.req
+ }
+ fmt.Fprintf(&buf, " req=%q", req)
+ return strings.TrimSpace(buf.String())
+}
+
+var proxyFromEnvTests = []proxyFromEnvTest{
+ {env: "127.0.0.1:8080", want: "http://127.0.0.1:8080"},
+ {env: "cache.corp.example.com:1234", want: "http://cache.corp.example.com:1234"},
+ {env: "cache.corp.example.com", want: "http://cache.corp.example.com"},
+ {env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"},
+ {env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"},
+ {env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"},
+ {want: "<nil>"},
+ {noenv: "example.com", req: "http://example.com/", env: "proxy", want: "<nil>"},
+ {noenv: ".example.com", req: "http://example.com/", env: "proxy", want: "<nil>"},
+ {noenv: "ample.com", req: "http://example.com/", env: "proxy", want: "http://proxy"},
+ {noenv: "example.com", req: "http://foo.example.com/", env: "proxy", want: "<nil>"},
+ {noenv: ".foo.com", req: "http://example.com/", env: "proxy", want: "http://proxy"},
}
func TestProxyFromEnvironment(t *testing.T) {
@@ -1134,16 +1158,21 @@ func TestProxyFromEnvironment(t *testing.T) {
os.Setenv("http_proxy", "")
os.Setenv("NO_PROXY", "")
os.Setenv("no_proxy", "")
- for i, tt := range proxyFromEnvTests {
+ for _, tt := range proxyFromEnvTests {
os.Setenv("HTTP_PROXY", tt.env)
- req, _ := NewRequest("GET", "http://example.com", nil)
+ os.Setenv("NO_PROXY", tt.noenv)
+ reqURL := tt.req
+ if reqURL == "" {
+ reqURL = "http://example.com"
+ }
+ req, _ := NewRequest("GET", reqURL, nil)
url, err := ProxyFromEnvironment(req)
if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e {
- t.Errorf("%d. got error = %q, want %q", i, g, e)
+ t.Errorf("%v: got error = %q, want %q", tt, g, e)
continue
}
- if got := fmt.Sprintf("%s", url); got != tt.wanturl {
- t.Errorf("%d. got URL = %q, want %q", i, url, tt.wanturl)
+ if got := fmt.Sprintf("%s", url); got != tt.want {
+ t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want)
}
}
}
diff --git a/libgo/go/net/interface_bsd.go b/libgo/go/net/interface_bsd.go
index 7f090d8d406..df9b3a2f279 100644
--- a/libgo/go/net/interface_bsd.go
+++ b/libgo/go/net/interface_bsd.go
@@ -118,7 +118,9 @@ func interfaceAddrTable(ifindex int) ([]Addr, error) {
if err != nil {
return nil, err
}
- ifat = append(ifat, ifa)
+ if ifa != nil {
+ ifat = append(ifat, ifa)
+ }
}
}
}
@@ -157,6 +159,8 @@ func newAddr(m *syscall.InterfaceAddrMessage) (Addr, error) {
ifa.IP[2], ifa.IP[3] = 0, 0
}
}
+ default: // Sockaddrs contain syscall.SockaddrDatalink on NetBSD
+ return nil, nil
}
}
return ifa, nil
diff --git a/libgo/go/net/interface_test.go b/libgo/go/net/interface_test.go
index 2fe0f60caea..803c1f4495c 100644
--- a/libgo/go/net/interface_test.go
+++ b/libgo/go/net/interface_test.go
@@ -75,9 +75,13 @@ func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) {
func testAddrs(t *testing.T, ifat []Addr) {
for _, ifa := range ifat {
- switch ifa.(type) {
+ switch v := ifa.(type) {
case *IPAddr, *IPNet:
- t.Logf("\tinterface address %q", ifa.String())
+ if v == nil {
+ t.Errorf("\tunexpected value: %v", ifa)
+ } else {
+ t.Logf("\tinterface address %q", ifa.String())
+ }
default:
t.Errorf("\tunexpected type: %T", ifa)
}
@@ -86,9 +90,13 @@ func testAddrs(t *testing.T, ifat []Addr) {
func testMulticastAddrs(t *testing.T, ifmat []Addr) {
for _, ifma := range ifmat {
- switch ifma.(type) {
+ switch v := ifma.(type) {
case *IPAddr:
- t.Logf("\tjoined group address %q", ifma.String())
+ if v == nil {
+ t.Errorf("\tunexpected value: %v", ifma)
+ } else {
+ t.Logf("\tjoined group address %q", ifma.String())
+ }
default:
t.Errorf("\tunexpected type: %T", ifma)
}
diff --git a/libgo/go/net/ip.go b/libgo/go/net/ip.go
index 0aac3d187a1..d588e3a4294 100644
--- a/libgo/go/net/ip.go
+++ b/libgo/go/net/ip.go
@@ -7,7 +7,7 @@
// IPv4 addresses are 4 bytes; IPv6 addresses are 16 bytes.
// An IPv4 address can be converted to an IPv6 address by
// adding a canonical prefix (10 zeros, 2 0xFFs).
-// This library accepts either size of byte array but always
+// This library accepts either size of byte slice but always
// returns 16-byte addresses.
package net
@@ -18,14 +18,14 @@ const (
IPv6len = 16
)
-// An IP is a single IP address, an array of bytes.
+// An IP is a single IP address, a slice of bytes.
// Functions in this package accept either 4-byte (IPv4)
-// or 16-byte (IPv6) arrays as input.
+// or 16-byte (IPv6) slices as input.
//
// Note that in this documentation, referring to an
// IP address as an IPv4 address or an IPv6 address
// is a semantic property of the address, not just the
-// length of the byte array: a 16-byte array can still
+// length of the byte slice: a 16-byte slice can still
// be an IPv4 address.
type IP []byte
diff --git a/libgo/go/net/ipraw_test.go b/libgo/go/net/ipraw_test.go
index f21889fcbea..db1c7694bb0 100644
--- a/libgo/go/net/ipraw_test.go
+++ b/libgo/go/net/ipraw_test.go
@@ -61,8 +61,7 @@ var icmpTests = []struct {
func TestICMP(t *testing.T) {
if os.Getuid() != 0 {
- t.Logf("skipping test; must be root")
- return
+ t.Skip("skipping test; must be root")
}
seqnum := 61455
@@ -253,8 +252,7 @@ var ipConnLocalNameTests = []struct {
func TestIPConnLocalName(t *testing.T) {
if os.Getuid() != 0 {
- t.Logf("skipping test; must be root")
- return
+ t.Skip("skipping test; must be root")
}
for _, tt := range ipConnLocalNameTests {
diff --git a/libgo/go/net/lookup_test.go b/libgo/go/net/lookup_test.go
index 990ade9e210..3355e469489 100644
--- a/libgo/go/net/lookup_test.go
+++ b/libgo/go/net/lookup_test.go
@@ -17,8 +17,7 @@ var testExternal = flag.Bool("external", true, "allow use of external networks d
func TestGoogleSRV(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
_, addrs, err := LookupSRV("xmpp-server", "tcp", "google.com")
if err != nil {
@@ -40,8 +39,7 @@ func TestGoogleSRV(t *testing.T) {
func TestGmailMX(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
mx, err := LookupMX("gmail.com")
if err != nil {
@@ -54,8 +52,7 @@ func TestGmailMX(t *testing.T) {
func TestGmailNS(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
ns, err := LookupNS("gmail.com")
if err != nil {
@@ -68,8 +65,7 @@ func TestGmailNS(t *testing.T) {
func TestGmailTXT(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
txt, err := LookupTXT("gmail.com")
if err != nil {
@@ -82,8 +78,7 @@ func TestGmailTXT(t *testing.T) {
func TestGoogleDNSAddr(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
names, err := LookupAddr("8.8.8.8")
if err != nil {
@@ -96,8 +91,7 @@ func TestGoogleDNSAddr(t *testing.T) {
func TestLookupIANACNAME(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
cname, err := LookupCNAME("www.iana.org")
if !strings.HasSuffix(cname, ".icann.org.") || err != nil {
diff --git a/libgo/go/net/multicast_posix_test.go b/libgo/go/net/multicast_posix_test.go
index bcc13ee8511..5850a6be0f7 100644
--- a/libgo/go/net/multicast_posix_test.go
+++ b/libgo/go/net/multicast_posix_test.go
@@ -48,12 +48,10 @@ var multicastListenerTests = []struct {
func TestMulticastListener(t *testing.T) {
switch runtime.GOOS {
case "netbsd", "openbsd", "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
case "linux":
if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" {
- t.Logf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
- return
+ t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
}
}
@@ -83,12 +81,10 @@ func TestMulticastListener(t *testing.T) {
func TestSimpleMulticastListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
case "windows":
if testing.Short() || !*testExternal {
- t.Logf("skipping test on windows to avoid firewall")
- return
+ t.Skip("skipping test on windows to avoid firewall")
}
}
diff --git a/libgo/go/net/net_test.go b/libgo/go/net/net_test.go
index a4e8dcd4455..8a560b52194 100644
--- a/libgo/go/net/net_test.go
+++ b/libgo/go/net/net_test.go
@@ -15,8 +15,7 @@ import (
func TestShutdown(t *testing.T) {
if runtime.GOOS == "plan9" {
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln, err := Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -63,8 +62,7 @@ func TestShutdown(t *testing.T) {
func TestShutdownUnix(t *testing.T) {
switch runtime.GOOS {
case "windows", "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
f, err := ioutil.TempFile("", "go_net_unixtest")
if err != nil {
@@ -145,8 +143,7 @@ func TestTCPListenClose(t *testing.T) {
func TestUDPListenClose(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln, err := ListenPacket("udp", "127.0.0.1:0")
if err != nil {
diff --git a/libgo/go/net/parse_test.go b/libgo/go/net/parse_test.go
index 30fda45dfd4..9df0c534b33 100644
--- a/libgo/go/net/parse_test.go
+++ b/libgo/go/net/parse_test.go
@@ -15,8 +15,7 @@ func TestReadLine(t *testing.T) {
// /etc/services file does not exist on windows and Plan 9.
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
filename := "/etc/services" // a nice big file
diff --git a/libgo/go/net/protoconn_test.go b/libgo/go/net/protoconn_test.go
index d99de3f138c..1344fba8a06 100644
--- a/libgo/go/net/protoconn_test.go
+++ b/libgo/go/net/protoconn_test.go
@@ -156,12 +156,10 @@ func TestUDPConnSpecificMethods(t *testing.T) {
func TestIPConnSpecificMethods(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping read test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping read test on %q", runtime.GOOS)
}
if os.Getuid() != 0 {
- t.Logf("skipping test; must be root")
- return
+ t.Skipf("skipping test; must be root")
}
la, err := net.ResolveIPAddr("ip4", "127.0.0.1")
@@ -212,8 +210,7 @@ func TestIPConnSpecificMethods(t *testing.T) {
func TestUnixListenerSpecificMethods(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping read test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping read test on %q", runtime.GOOS)
}
p := "/tmp/gotest.net"
@@ -259,8 +256,7 @@ func TestUnixListenerSpecificMethods(t *testing.T) {
func TestUnixConnSpecificMethods(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
p1, p2, p3 := "/tmp/gotest.net1", "/tmp/gotest.net2", "/tmp/gotest.net3"
diff --git a/libgo/go/net/server_test.go b/libgo/go/net/server_test.go
index 158b9477d03..eba1e7d9691 100644
--- a/libgo/go/net/server_test.go
+++ b/libgo/go/net/server_test.go
@@ -142,8 +142,7 @@ var seqpacketConnServerTests = []struct {
func TestSeqpacketConnServer(t *testing.T) {
if runtime.GOOS != "linux" {
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
for _, tt := range seqpacketConnServerTests {
diff --git a/libgo/go/net/sock_cloexec.go b/libgo/go/net/sock_cloexec.go
new file mode 100644
index 00000000000..e2a5ef7160a
--- /dev/null
+++ b/libgo/go/net/sock_cloexec.go
@@ -0,0 +1,69 @@
+// 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.
+
+// This file implements sysSocket and accept for platforms that
+// provide a fast path for setting SetNonblock and CloseOnExec.
+
+// +build linux
+
+package net
+
+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 {
+ return s, err
+ }
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err = syscall.Socket(f, t, p)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return -1, err
+ }
+ if err = syscall.SetNonblock(s, true); err != nil {
+ syscall.Close(s)
+ return -1, err
+ }
+ return s, nil
+}
+
+// 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 error, fall back to using accept.
+ if err == nil || err != syscall.ENOSYS {
+ return nfd, sa, err
+ }
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ // It is okay to hold the lock across syscall.Accept
+ // because we have put fd.sysfd into non-blocking mode.
+ syscall.ForkLock.RLock()
+ nfd, sa, err = syscall.Accept(fd)
+ if err == nil {
+ syscall.CloseOnExec(nfd)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return -1, nil, err
+ }
+ if err = syscall.SetNonblock(nfd, true); err != nil {
+ syscall.Close(nfd)
+ return -1, nil, err
+ }
+ return nfd, sa, nil
+}
diff --git a/libgo/go/net/sock_posix.go b/libgo/go/net/sock_posix.go
index 12015ef0acd..9cd149e466b 100644
--- a/libgo/go/net/sock_posix.go
+++ b/libgo/go/net/sock_posix.go
@@ -17,15 +17,10 @@ var listenerBacklog = maxListenerBacklog()
// Generic socket creation.
func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
- // See ../syscall/exec_unix.go for description of ForkLock.
- syscall.ForkLock.RLock()
- s, err := syscall.Socket(f, t, p)
+ s, err := sysSocket(f, t, p)
if err != nil {
- syscall.ForkLock.RUnlock()
return nil, err
}
- syscall.CloseOnExec(s)
- syscall.ForkLock.RUnlock()
if err = setDefaultSockopts(s, f, t, ipv6only); err != nil {
closesocket(s)
diff --git a/libgo/go/net/sock_windows.go b/libgo/go/net/sock_windows.go
index cce6181c9e5..fc5d9e5de21 100644
--- a/libgo/go/net/sock_windows.go
+++ b/libgo/go/net/sock_windows.go
@@ -41,3 +41,14 @@ func listenerSockaddr(s syscall.Handle, f int, la syscall.Sockaddr, toAddr func(
}
return la, nil
}
+
+func sysSocket(f, t, p int) (syscall.Handle, error) {
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err := syscall.Socket(f, t, p)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
+ return s, err
+}
diff --git a/libgo/go/net/sys_cloexec.go b/libgo/go/net/sys_cloexec.go
new file mode 100644
index 00000000000..75d5688a163
--- /dev/null
+++ b/libgo/go/net/sys_cloexec.go
@@ -0,0 +1,54 @@
+// 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.
+
+// This file implements sysSocket and accept for platforms that do not
+// provide a fast path for setting SetNonblock and CloseOnExec.
+
+// +build darwin freebsd netbsd openbsd
+
+package net
+
+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) {
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err := syscall.Socket(f, t, p)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return -1, err
+ }
+ if err = syscall.SetNonblock(s, true); err != nil {
+ syscall.Close(s)
+ return -1, err
+ }
+ return s, nil
+}
+
+// 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) {
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ // It is okay to hold the lock across syscall.Accept
+ // because we have put fd.sysfd into non-blocking mode.
+ syscall.ForkLock.RLock()
+ nfd, sa, err := syscall.Accept(fd)
+ if err == nil {
+ syscall.CloseOnExec(nfd)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return -1, nil, err
+ }
+ if err = syscall.SetNonblock(nfd, true); err != nil {
+ syscall.Close(nfd)
+ return -1, nil, err
+ }
+ return nfd, sa, nil
+}
diff --git a/libgo/go/net/tcp_test.go b/libgo/go/net/tcp_test.go
index bca748827ce..1d54b3adcc7 100644
--- a/libgo/go/net/tcp_test.go
+++ b/libgo/go/net/tcp_test.go
@@ -159,8 +159,7 @@ var tcpListenerNameTests = []struct {
func TestTCPListenerName(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
for _, tt := range tcpListenerNameTests {
diff --git a/libgo/go/net/tcpsock_posix.go b/libgo/go/net/tcpsock_posix.go
index 4f9159566f3..bd5a2a28775 100644
--- a/libgo/go/net/tcpsock_posix.go
+++ b/libgo/go/net/tcpsock_posix.go
@@ -26,11 +26,6 @@ func sockaddrToTCP(sa syscall.Sockaddr) Addr {
return &TCPAddr{IP: sa.Addr[0:], Port: sa.Port}
case *syscall.SockaddrInet6:
return &TCPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneToString(int(sa.ZoneId))}
- default:
- if sa != nil {
- // Diagnose when we will turn a non-nil sockaddr into a nil.
- panic("unexpected type in sockaddrToTCP")
- }
}
return nil
}
diff --git a/libgo/go/net/timeout_test.go b/libgo/go/net/timeout_test.go
index 21223cc74ad..7cf45ca0a09 100644
--- a/libgo/go/net/timeout_test.go
+++ b/libgo/go/net/timeout_test.go
@@ -27,8 +27,7 @@ type copyRes struct {
func TestAcceptTimeout(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln := newLocalListener(t).(*TCPListener)
@@ -60,16 +59,22 @@ func TestAcceptTimeout(t *testing.T) {
default:
}
ln.Close()
- if err := <-errc; err.(*OpError).Err != errClosing {
- t.Fatalf("Accept: expected err %v, got %v", errClosing, err.(*OpError).Err)
+ switch nerr := <-errc; err := nerr.(type) {
+ case *OpError:
+ if err.Err != errClosing {
+ t.Fatalf("Accept: expected err %v, got %v", errClosing, err)
+ }
+ default:
+ if err != errClosing {
+ t.Fatalf("Accept: expected err %v, got %v", errClosing, err)
+ }
}
}
func TestReadTimeout(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln := newLocalListener(t)
@@ -109,16 +114,22 @@ func TestReadTimeout(t *testing.T) {
default:
}
c.Close()
- if err := <-errc; err.(*OpError).Err != errClosing {
- t.Fatalf("Read: expected err %v, got %v", errClosing, err.(*OpError).Err)
+ switch nerr := <-errc; err := nerr.(type) {
+ case *OpError:
+ if err.Err != errClosing {
+ t.Fatalf("Read: expected err %v, got %v", errClosing, err)
+ }
+ default:
+ if err != errClosing {
+ t.Fatalf("Read: expected err %v, got %v", errClosing, err)
+ }
}
}
func TestWriteTimeout(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln := newLocalListener(t)
@@ -164,8 +175,15 @@ func TestWriteTimeout(t *testing.T) {
default:
}
c.Close()
- if err := <-errc; err.(*OpError).Err != errClosing {
- t.Fatalf("Write: expected err %v, got %v", errClosing, err.(*OpError).Err)
+ switch nerr := <-errc; err := nerr.(type) {
+ case *OpError:
+ if err.Err != errClosing {
+ t.Fatalf("Write: expected err %v, got %v", errClosing, err)
+ }
+ default:
+ if err != errClosing {
+ t.Fatalf("Write: expected err %v, got %v", errClosing, err)
+ }
}
}
@@ -217,8 +235,7 @@ func testTimeout(t *testing.T, net, addr string, readFrom bool) {
func TestTimeoutUDP(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
// set up a listener that won't talk back
@@ -235,8 +252,7 @@ func TestTimeoutUDP(t *testing.T) {
func TestTimeoutTCP(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
// set up a listener that won't talk back
@@ -252,8 +268,7 @@ func TestTimeoutTCP(t *testing.T) {
func TestDeadlineReset(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln, err := Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -281,8 +296,7 @@ func TestDeadlineReset(t *testing.T) {
func TestTimeoutAccept(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln, err := Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -308,13 +322,11 @@ func TestTimeoutAccept(t *testing.T) {
func TestReadWriteDeadline(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
if !canCancelIO {
- t.Logf("skipping test on this system")
- return
+ t.Skip("skipping test on this system")
}
const (
readTimeout = 50 * time.Millisecond
@@ -574,8 +586,7 @@ func TestWriteDeadlineBufferAvailable(t *testing.T) {
func TestProlongTimeout(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
ln := newLocalListener(t)
diff --git a/libgo/go/net/udp_test.go b/libgo/go/net/udp_test.go
index d3594b40a9e..220422e132e 100644
--- a/libgo/go/net/udp_test.go
+++ b/libgo/go/net/udp_test.go
@@ -43,8 +43,7 @@ func TestResolveUDPAddr(t *testing.T) {
func TestWriteToUDP(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
l, err := ListenPacket("udp", "127.0.0.1:0")
@@ -130,8 +129,7 @@ var udpConnLocalNameTests = []struct {
func TestUDPConnLocalName(t *testing.T) {
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
for _, tt := range udpConnLocalNameTests {
diff --git a/libgo/go/net/udpsock_posix.go b/libgo/go/net/udpsock_posix.go
index b7de678f928..385cd902eb8 100644
--- a/libgo/go/net/udpsock_posix.go
+++ b/libgo/go/net/udpsock_posix.go
@@ -211,25 +211,22 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e
return nil, UnknownNetworkError(net)
}
if gaddr == nil || gaddr.IP == nil {
- return nil, &OpError{"listenmulticast", net, nil, errMissingAddress}
+ return nil, &OpError{"listen", net, nil, errMissingAddress}
}
fd, err := internetSocket(net, gaddr.toAddr(), nil, noDeadline, syscall.SOCK_DGRAM, 0, "listen", sockaddrToUDP)
if err != nil {
return nil, err
}
c := newUDPConn(fd)
- ip4 := gaddr.IP.To4()
- if ip4 != nil {
- err := listenIPv4MulticastUDP(c, ifi, ip4)
- if err != nil {
+ if ip4 := gaddr.IP.To4(); ip4 != nil {
+ if err := listenIPv4MulticastUDP(c, ifi, ip4); err != nil {
c.Close()
- return nil, err
+ return nil, &OpError{"listen", net, &IPAddr{IP: ip4}, err}
}
} else {
- err := listenIPv6MulticastUDP(c, ifi, gaddr.IP)
- if err != nil {
+ if err := listenIPv6MulticastUDP(c, ifi, gaddr.IP); err != nil {
c.Close()
- return nil, err
+ return nil, &OpError{"listen", net, &IPAddr{IP: gaddr.IP}, err}
}
}
return c, nil
@@ -237,17 +234,14 @@ func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, e
func listenIPv4MulticastUDP(c *UDPConn, ifi *Interface, ip IP) error {
if ifi != nil {
- err := setIPv4MulticastInterface(c.fd, ifi)
- if err != nil {
+ if err := setIPv4MulticastInterface(c.fd, ifi); err != nil {
return err
}
}
- err := setIPv4MulticastLoopback(c.fd, false)
- if err != nil {
+ if err := setIPv4MulticastLoopback(c.fd, false); err != nil {
return err
}
- err = joinIPv4GroupUDP(c, ifi, ip)
- if err != nil {
+ if err := joinIPv4Group(c.fd, ifi, ip); err != nil {
return err
}
return nil
@@ -255,34 +249,15 @@ func listenIPv4MulticastUDP(c *UDPConn, ifi *Interface, ip IP) error {
func listenIPv6MulticastUDP(c *UDPConn, ifi *Interface, ip IP) error {
if ifi != nil {
- err := setIPv6MulticastInterface(c.fd, ifi)
- if err != nil {
+ if err := setIPv6MulticastInterface(c.fd, ifi); err != nil {
return err
}
}
- err := setIPv6MulticastLoopback(c.fd, false)
- if err != nil {
+ if err := setIPv6MulticastLoopback(c.fd, false); err != nil {
return err
}
- err = joinIPv6GroupUDP(c, ifi, ip)
- if err != nil {
+ if err := joinIPv6Group(c.fd, ifi, ip); err != nil {
return err
}
return nil
}
-
-func joinIPv4GroupUDP(c *UDPConn, ifi *Interface, ip IP) error {
- err := joinIPv4Group(c.fd, ifi, ip)
- if err != nil {
- return &OpError{"joinipv4group", c.fd.net, &IPAddr{IP: ip}, err}
- }
- return nil
-}
-
-func joinIPv6GroupUDP(c *UDPConn, ifi *Interface, ip IP) error {
- err := joinIPv6Group(c.fd, ifi, ip)
- if err != nil {
- return &OpError{"joinipv6group", c.fd.net, &IPAddr{IP: ip}, err}
- }
- return nil
-}
diff --git a/libgo/go/net/unicast_posix_test.go b/libgo/go/net/unicast_posix_test.go
index e1d4b0d47aa..a8855cab7da 100644
--- a/libgo/go/net/unicast_posix_test.go
+++ b/libgo/go/net/unicast_posix_test.go
@@ -46,8 +46,7 @@ var listenerTests = []struct {
func TestTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
for _, tt := range listenerTests {
@@ -71,8 +70,7 @@ func TestTCPListener(t *testing.T) {
func TestUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
toudpnet := func(net string) string {
@@ -106,7 +104,7 @@ func TestUDPListener(t *testing.T) {
func TestSimpleTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
+ t.Skipf("skipping test on %q", runtime.GOOS)
return
}
@@ -128,7 +126,7 @@ func TestSimpleTCPListener(t *testing.T) {
func TestSimpleUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
+ t.Skipf("skipping test on %q", runtime.GOOS)
return
}
@@ -230,8 +228,7 @@ var dualStackListenerTests = []struct {
func TestDualStackTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
if !supportsIPv6 {
return
@@ -263,8 +260,7 @@ func TestDualStackTCPListener(t *testing.T) {
func TestDualStackUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
if !supportsIPv6 {
return
@@ -467,8 +463,7 @@ var prohibitionaryDialArgTests = []struct {
func TestProhibitionaryDialArgs(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
// This test requires both IPv6 and IPv6 IPv4-mapping functionality.
if !supportsIPv4map || testing.Short() || !*testExternal {
@@ -490,13 +485,11 @@ func TestProhibitionaryDialArgs(t *testing.T) {
func TestWildWildcardListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
- t.Logf("skipping test on %q", runtime.GOOS)
- return
+ t.Skipf("skipping test on %q", runtime.GOOS)
}
if testing.Short() || !*testExternal {
- t.Logf("skipping test to avoid external network")
- return
+ t.Skip("skipping test to avoid external network")
}
defer func() {
diff --git a/libgo/go/net/url/url.go b/libgo/go/net/url/url.go
index 71758fe49e0..68f2c2f6e7e 100644
--- a/libgo/go/net/url/url.go
+++ b/libgo/go/net/url/url.go
@@ -386,7 +386,7 @@ func parse(rawurl string, viaRequest bool) (url *URL, err error) {
}
}
- if (url.Scheme != "" || !viaRequest) && strings.HasPrefix(rest, "//") && !strings.HasPrefix(rest, "///") {
+ if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
var authority string
authority, rest = split(rest[2:], '/', false)
url.User, url.Host, err = parseAuthority(authority)
@@ -434,30 +434,35 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) {
// String reassembles the URL into a valid URL string.
func (u *URL) String() string {
- // TODO: Rewrite to use bytes.Buffer
- result := ""
+ var buf bytes.Buffer
if u.Scheme != "" {
- result += u.Scheme + ":"
+ buf.WriteString(u.Scheme)
+ buf.WriteByte(':')
}
if u.Opaque != "" {
- result += u.Opaque
+ buf.WriteString(u.Opaque)
} else {
- if u.Host != "" || u.User != nil {
- result += "//"
+ if u.Scheme != "" || u.Host != "" || u.User != nil {
+ buf.WriteString("//")
if u := u.User; u != nil {
- result += u.String() + "@"
+ buf.WriteString(u.String())
+ buf.WriteByte('@')
+ }
+ if h := u.Host; h != "" {
+ buf.WriteString(h)
}
- result += u.Host
}
- result += escape(u.Path, encodePath)
+ buf.WriteString(escape(u.Path, encodePath))
}
if u.RawQuery != "" {
- result += "?" + u.RawQuery
+ buf.WriteByte('?')
+ buf.WriteString(u.RawQuery)
}
if u.Fragment != "" {
- result += "#" + escape(u.Fragment, encodeFragment)
+ buf.WriteByte('#')
+ buf.WriteString(escape(u.Fragment, encodeFragment))
}
- return result
+ return buf.String()
}
// Values maps a string key to a list of values.
diff --git a/libgo/go/net/url/url_test.go b/libgo/go/net/url/url_test.go
index 4d3545dadb7..cd3b0b9e8c7 100644
--- a/libgo/go/net/url/url_test.go
+++ b/libgo/go/net/url/url_test.go
@@ -122,14 +122,14 @@ var urltests = []URLTest{
},
"http:%2f%2fwww.google.com/?q=go+language",
},
- // non-authority
+ // non-authority with path
{
"mailto:/webmaster@golang.org",
&URL{
Scheme: "mailto",
Path: "/webmaster@golang.org",
},
- "",
+ "mailto:///webmaster@golang.org", // unfortunate compromise
},
// non-authority
{
@@ -242,6 +242,15 @@ var urltests = []URLTest{
},
"http://www.google.com/?q=go+language#foo&bar",
},
+ {
+ "file:///home/adg/rabbits",
+ &URL{
+ Scheme: "file",
+ Host: "",
+ Path: "/home/adg/rabbits",
+ },
+ "file:///home/adg/rabbits",
+ },
}
// more useful string for debugging than fmt's struct printer
@@ -271,6 +280,30 @@ func DoTest(t *testing.T, parse func(string) (*URL, error), name string, tests [
}
}
+func BenchmarkString(b *testing.B) {
+ b.StopTimer()
+ b.ReportAllocs()
+ for _, tt := range urltests {
+ u, err := Parse(tt.in)
+ if err != nil {
+ b.Errorf("Parse(%q) returned error %s", tt.in, err)
+ continue
+ }
+ if tt.roundtrip == "" {
+ continue
+ }
+ b.StartTimer()
+ var g string
+ for i := 0; i < b.N; i++ {
+ g = u.String()
+ }
+ b.StopTimer()
+ if w := tt.roundtrip; g != w {
+ b.Errorf("Parse(%q).String() == %q, want %q", tt.in, g, w)
+ }
+ }
+}
+
func TestParse(t *testing.T) {
DoTest(t, Parse, "Parse", urltests)
}