diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-01-29 20:52:43 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2013-01-29 20:52:43 +0000 |
commit | 12ebd6294172cc1108bbadab78fea03e890a6da4 (patch) | |
tree | 4f2fad1f4b778519bdd5941185c7e1d032af055b /libgo/go/net | |
parent | 6effa4dc115122a3a4838de0a302dfcadcefeeca (diff) | |
download | gcc-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')
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) } |