summaryrefslogtreecommitdiff
path: root/libgo/go/net/http
diff options
context:
space:
mode:
authorian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2012-03-02 16:38:43 +0000
committerian <ian@138bc75d-0d04-0410-961f-82ee72b054a4>2012-03-02 16:38:43 +0000
commit2d2d80b8bd963f59534897b3d51ef8bd546cb4bc (patch)
treeefa0c55763b34cbc633bc494c2743d1b5d9aaff3 /libgo/go/net/http
parent2ad2700dbf70b2e49575f3f2307839a45cf2f71c (diff)
downloadgcc-2d2d80b8bd963f59534897b3d51ef8bd546cb4bc.tar.gz
libgo: Update to weekly.2012-02-14 release.
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@184798 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/net/http')
-rw-r--r--libgo/go/net/http/cgi/host_test.go3
-rw-r--r--libgo/go/net/http/doc.go4
-rw-r--r--libgo/go/net/http/fcgi/child.go4
-rw-r--r--libgo/go/net/http/fs.go211
-rw-r--r--libgo/go/net/http/fs_test.go145
-rw-r--r--libgo/go/net/http/httptest/server.go4
-rw-r--r--libgo/go/net/http/httptest/server_test.go29
-rw-r--r--libgo/go/net/http/pprof/pprof.go22
-rw-r--r--libgo/go/net/http/response_test.go6
-rw-r--r--libgo/go/net/http/server.go74
-rw-r--r--libgo/go/net/http/sniff.go14
-rw-r--r--libgo/go/net/http/transport.go35
-rw-r--r--libgo/go/net/http/transport_test.go73
-rw-r--r--libgo/go/net/http/triv.go2
14 files changed, 474 insertions, 152 deletions
diff --git a/libgo/go/net/http/cgi/host_test.go b/libgo/go/net/http/cgi/host_test.go
index b8dbdb4edd2..fec35b72f99 100644
--- a/libgo/go/net/http/cgi/host_test.go
+++ b/libgo/go/net/http/cgi/host_test.go
@@ -19,6 +19,7 @@ import (
"runtime"
"strconv"
"strings"
+ "syscall"
"testing"
"time"
)
@@ -355,7 +356,7 @@ func TestCopyError(t *testing.T) {
if err != nil {
return false
}
- return p.Signal(os.UnixSignal(0)) == nil
+ return p.Signal(syscall.Signal(0)) == nil
}
if !childRunning() {
diff --git a/libgo/go/net/http/doc.go b/libgo/go/net/http/doc.go
index 8962ed31e6a..b6ae8b87a2f 100644
--- a/libgo/go/net/http/doc.go
+++ b/libgo/go/net/http/doc.go
@@ -12,7 +12,7 @@ Get, Head, Post, and PostForm make HTTP requests:
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
- url.Values{"key": {"Value"}, "id": {"123"}})
+ url.Values{"key": {"Value"}, "id": {"123"}})
The client must close the response body when finished with it:
@@ -60,7 +60,7 @@ Handle and HandleFunc add handlers to DefaultServeMux:
http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.RawPath))
+ fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))
diff --git a/libgo/go/net/http/fcgi/child.go b/libgo/go/net/http/fcgi/child.go
index c94b9a7b249..c8b9a33c87b 100644
--- a/libgo/go/net/http/fcgi/child.go
+++ b/libgo/go/net/http/fcgi/child.go
@@ -243,9 +243,9 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {
}
// Serve accepts incoming FastCGI connections on the listener l, creating a new
-// service thread for each. The service threads read requests and then call handler
+// goroutine for each. The goroutine reads requests and then calls handler
// to reply to them.
-// If l is nil, Serve accepts connections on stdin.
+// If l is nil, Serve accepts connections from os.Stdin.
// If handler is nil, http.DefaultServeMux is used.
func Serve(l net.Listener, handler http.Handler) error {
if l == nil {
diff --git a/libgo/go/net/http/fs.go b/libgo/go/net/http/fs.go
index 1392ca68ad6..f35dd32c305 100644
--- a/libgo/go/net/http/fs.go
+++ b/libgo/go/net/http/fs.go
@@ -17,7 +17,6 @@ import (
"strconv"
"strings"
"time"
- "unicode/utf8"
)
// A Dir implements http.FileSystem using the native file
@@ -58,32 +57,6 @@ type File interface {
Seek(offset int64, whence int) (int64, error)
}
-// Heuristic: b is text if it is valid UTF-8 and doesn't
-// contain any unprintable ASCII or Unicode characters.
-func isText(b []byte) bool {
- for len(b) > 0 && utf8.FullRune(b) {
- rune, size := utf8.DecodeRune(b)
- if size == 1 && rune == utf8.RuneError {
- // decoding error
- return false
- }
- if 0x7F <= rune && rune <= 0x9F {
- return false
- }
- if rune < ' ' {
- switch rune {
- case '\n', '\r', '\t':
- // okay
- default:
- // binary garbage
- return false
- }
- }
- b = b[size:]
- }
- return true
-}
-
func dirList(w ResponseWriter, f File) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<pre>\n")
@@ -104,6 +77,126 @@ func dirList(w ResponseWriter, f File) {
fmt.Fprintf(w, "</pre>\n")
}
+// ServeContent replies to the request using the content in the
+// provided ReadSeeker. The main benefit of ServeContent over io.Copy
+// is that it handles Range requests properly, sets the MIME type, and
+// handles If-Modified-Since requests.
+//
+// If the response's Content-Type header is not set, ServeContent
+// first tries to deduce the type from name's file extension and,
+// if that fails, falls back to reading the first block of the content
+// and passing it to DetectContentType.
+// The name is otherwise unused; in particular it can be empty and is
+// never sent in the response.
+//
+// If modtime is not the zero time, ServeContent includes it in a
+// Last-Modified header in the response. If the request includes an
+// If-Modified-Since header, ServeContent uses modtime to decide
+// whether the content needs to be sent at all.
+//
+// The content's Seek method must work: ServeContent uses
+// a seek to the end of the content to determine its size.
+//
+// Note that *os.File implements the io.ReadSeeker interface.
+func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
+ size, err := content.Seek(0, os.SEEK_END)
+ if err != nil {
+ Error(w, "seeker can't seek", StatusInternalServerError)
+ return
+ }
+ _, err = content.Seek(0, os.SEEK_SET)
+ if err != nil {
+ Error(w, "seeker can't seek", StatusInternalServerError)
+ return
+ }
+ serveContent(w, req, name, modtime, size, content)
+}
+
+// if name is empty, filename is unknown. (used for mime type, before sniffing)
+// if modtime.IsZero(), modtime is unknown.
+// content must be seeked to the beginning of the file.
+func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) {
+ if checkLastModified(w, r, modtime) {
+ return
+ }
+
+ code := StatusOK
+
+ // If Content-Type isn't set, use the file's extension to find it.
+ if w.Header().Get("Content-Type") == "" {
+ ctype := mime.TypeByExtension(filepath.Ext(name))
+ if ctype == "" {
+ // read a chunk to decide between utf-8 text and binary
+ var buf [1024]byte
+ n, _ := io.ReadFull(content, buf[:])
+ b := buf[:n]
+ ctype = DetectContentType(b)
+ _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file
+ if err != nil {
+ Error(w, "seeker can't seek", StatusInternalServerError)
+ return
+ }
+ }
+ w.Header().Set("Content-Type", ctype)
+ }
+
+ // handle Content-Range header.
+ // TODO(adg): handle multiple ranges
+ sendSize := size
+ if size >= 0 {
+ ranges, err := parseRange(r.Header.Get("Range"), size)
+ if err == nil && len(ranges) > 1 {
+ err = errors.New("multiple ranges not supported")
+ }
+ if err != nil {
+ Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
+ return
+ }
+ if len(ranges) == 1 {
+ ra := ranges[0]
+ if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil {
+ Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
+ return
+ }
+ sendSize = ra.length
+ code = StatusPartialContent
+ w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size))
+ }
+
+ w.Header().Set("Accept-Ranges", "bytes")
+ if w.Header().Get("Content-Encoding") == "" {
+ w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
+ }
+ }
+
+ w.WriteHeader(code)
+
+ if r.Method != "HEAD" {
+ if sendSize == -1 {
+ io.Copy(w, content)
+ } else {
+ io.CopyN(w, content, sendSize)
+ }
+ }
+}
+
+// modtime is the modification time of the resource to be served, or IsZero().
+// return value is whether this request is now complete.
+func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool {
+ if modtime.IsZero() {
+ return false
+ }
+
+ // The Date-Modified header truncates sub-second precision, so
+ // use mtime < t+1s instead of mtime <= t to check for unmodified.
+ if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
+ w.WriteHeader(StatusNotModified)
+ return true
+ }
+ w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
+ return false
+}
+
// name is '/'-separated, not filepath.Separator.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
const indexPage = "/index.html"
@@ -148,14 +241,11 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
}
}
- if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && !d.ModTime().After(t) {
- w.WriteHeader(StatusNotModified)
- return
- }
- w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
-
// use contents of index.html for directory, if present
if d.IsDir() {
+ if checkLastModified(w, r, d.ModTime()) {
+ return
+ }
index := name + indexPage
ff, err := fs.Open(index)
if err == nil {
@@ -174,60 +264,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
return
}
- // serve file
- size := d.Size()
- code := StatusOK
-
- // If Content-Type isn't set, use the file's extension to find it.
- if w.Header().Get("Content-Type") == "" {
- ctype := mime.TypeByExtension(filepath.Ext(name))
- if ctype == "" {
- // read a chunk to decide between utf-8 text and binary
- var buf [1024]byte
- n, _ := io.ReadFull(f, buf[:])
- b := buf[:n]
- if isText(b) {
- ctype = "text/plain; charset=utf-8"
- } else {
- // generic binary
- ctype = "application/octet-stream"
- }
- f.Seek(0, os.SEEK_SET) // rewind to output whole file
- }
- w.Header().Set("Content-Type", ctype)
- }
-
- // handle Content-Range header.
- // TODO(adg): handle multiple ranges
- ranges, err := parseRange(r.Header.Get("Range"), size)
- if err == nil && len(ranges) > 1 {
- err = errors.New("multiple ranges not supported")
- }
- if err != nil {
- Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
- return
- }
- if len(ranges) == 1 {
- ra := ranges[0]
- if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
- Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
- return
- }
- size = ra.length
- code = StatusPartialContent
- w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size()))
- }
-
- w.Header().Set("Accept-Ranges", "bytes")
- if w.Header().Get("Content-Encoding") == "" {
- w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
- }
-
- w.WriteHeader(code)
-
- if r.Method != "HEAD" {
- io.CopyN(w, f, size)
- }
+ serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f)
}
// localRedirect gives a Moved Permanently response.
diff --git a/libgo/go/net/http/fs_test.go b/libgo/go/net/http/fs_test.go
index feea9209e6a..143617e95fc 100644
--- a/libgo/go/net/http/fs_test.go
+++ b/libgo/go/net/http/fs_test.go
@@ -5,15 +5,22 @@
package http_test
import (
+ "bytes"
"fmt"
+ "io"
"io/ioutil"
+ "net"
. "net/http"
"net/http/httptest"
"net/url"
"os"
+ "os/exec"
"path/filepath"
+ "regexp"
+ "runtime"
"strings"
"testing"
+ "time"
)
const (
@@ -56,18 +63,18 @@ func TestServeFile(t *testing.T) {
req.Method = "GET"
// straight GET
- _, body := getBody(t, req)
+ _, body := getBody(t, "straight get", req)
if !equal(body, file) {
t.Fatalf("body mismatch: got %q, want %q", body, file)
}
// Range tests
- for _, rt := range ServeFileRangeTests {
+ for i, rt := range ServeFileRangeTests {
req.Header.Set("Range", "bytes="+rt.r)
if rt.r == "" {
req.Header["Range"] = nil
}
- r, body := getBody(t, req)
+ r, body := getBody(t, fmt.Sprintf("test %d", i), req)
if r.StatusCode != rt.code {
t.Errorf("range=%q: StatusCode=%d, want %d", rt.r, r.StatusCode, rt.code)
}
@@ -298,7 +305,6 @@ func TestServeIndexHtml(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- defer res.Body.Close()
b, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal("reading Body:", err)
@@ -306,21 +312,146 @@ func TestServeIndexHtml(t *testing.T) {
if s := string(b); s != want {
t.Errorf("for path %q got %q, want %q", path, s, want)
}
+ res.Body.Close()
+ }
+}
+
+func TestServeContent(t *testing.T) {
+ type req struct {
+ name string
+ modtime time.Time
+ content io.ReadSeeker
+ }
+ ch := make(chan req, 1)
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ p := <-ch
+ ServeContent(w, r, p.name, p.modtime, p.content)
+ }))
+ defer ts.Close()
+
+ css, err := os.Open("testdata/style.css")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer css.Close()
+
+ ch <- req{"style.css", time.Time{}, css}
+ res, err := Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if g, e := res.Header.Get("Content-Type"), "text/css; charset=utf-8"; g != e {
+ t.Errorf("style.css: content type = %q, want %q", g, e)
+ }
+ if g := res.Header.Get("Last-Modified"); g != "" {
+ t.Errorf("want empty Last-Modified; got %q", g)
+ }
+
+ fi, err := css.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ ch <- req{"style.html", fi.ModTime(), css}
+ res, err = Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if g, e := res.Header.Get("Content-Type"), "text/html; charset=utf-8"; g != e {
+ t.Errorf("style.html: content type = %q, want %q", g, e)
+ }
+ if g := res.Header.Get("Last-Modified"); g == "" {
+ t.Errorf("want non-empty last-modified")
}
}
-func getBody(t *testing.T, req Request) (*Response, []byte) {
+// verifies that sendfile is being used on Linux
+func TestLinuxSendfile(t *testing.T) {
+ if runtime.GOOS != "linux" {
+ t.Logf("skipping; linux-only test")
+ return
+ }
+ _, err := exec.LookPath("strace")
+ if err != nil {
+ t.Logf("skipping; strace not found in path")
+ return
+ }
+
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ lnf, err := ln.(*net.TCPListener).File()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ var buf bytes.Buffer
+ child := exec.Command("strace", "-f", os.Args[0], "-test.run=TestLinuxSendfileChild")
+ child.ExtraFiles = append(child.ExtraFiles, lnf)
+ 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
+ }
+
+ _, err = Get(fmt.Sprintf("http://%s/", ln.Addr()))
+ if err != nil {
+ t.Errorf("http client error: %v", err)
+ return
+ }
+
+ // Force child to exit cleanly.
+ Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
+ child.Wait()
+
+ rx := regexp.MustCompile(`sendfile(64)?\(\d+,\s*\d+,\s*NULL,\s*\d+\)\s*=\s*\d+\s*\n`)
+ rxResume := regexp.MustCompile(`<\.\.\. sendfile(64)? resumed> \)\s*=\s*\d+\s*\n`)
+ out := buf.String()
+ if !rx.MatchString(out) && !rxResume.MatchString(out) {
+ t.Errorf("no sendfile system call found in:\n%s", out)
+ }
+}
+
+func getBody(t *testing.T, testName string, req Request) (*Response, []byte) {
r, err := DefaultClient.Do(&req)
if err != nil {
- t.Fatal(req.URL.String(), "send:", err)
+ t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err)
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
- t.Fatal("reading Body:", err)
+ t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err)
}
return r, b
}
+// TestLinuxSendfileChild isn't a real test. It's used as a helper process
+// for TestLinuxSendfile.
+func TestLinuxSendfileChild(*testing.T) {
+ if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
+ return
+ }
+ defer os.Exit(0)
+ fd3 := os.NewFile(3, "ephemeral-port-listener")
+ ln, err := net.FileListener(fd3)
+ if err != nil {
+ panic(err)
+ }
+ mux := NewServeMux()
+ mux.Handle("/", FileServer(Dir("testdata")))
+ mux.HandleFunc("/quit", func(ResponseWriter, *Request) {
+ os.Exit(0)
+ })
+ s := &Server{Handler: mux}
+ err = s.Serve(ln)
+ if err != nil {
+ panic(err)
+ }
+}
+
func equal(a, b []byte) bool {
if len(a) != len(b) {
return false
diff --git a/libgo/go/net/http/httptest/server.go b/libgo/go/net/http/httptest/server.go
index 5b02e143d4a..8d911f7575b 100644
--- a/libgo/go/net/http/httptest/server.go
+++ b/libgo/go/net/http/httptest/server.go
@@ -61,7 +61,7 @@ func newLocalListener() net.Listener {
// When debugging a particular http server-based test,
// this flag lets you run
-// gotest -run=BrokenTest -httptest.serve=127.0.0.1:8000
+// go test -run=BrokenTest -httptest.serve=127.0.0.1:8000
// to start the broken server so you can interact with it manually.
var serve = flag.String("httptest.serve", "", "if non-empty, httptest.NewServer serves on this address and blocks")
@@ -95,7 +95,7 @@ func (s *Server) Start() {
s.URL = "http://" + s.Listener.Addr().String()
go s.Config.Serve(s.Listener)
if *serve != "" {
- fmt.Println(os.Stderr, "httptest: serving on", s.URL)
+ fmt.Fprintln(os.Stderr, "httptest: serving on", s.URL)
select {}
}
}
diff --git a/libgo/go/net/http/httptest/server_test.go b/libgo/go/net/http/httptest/server_test.go
new file mode 100644
index 00000000000..500a9f0b800
--- /dev/null
+++ b/libgo/go/net/http/httptest/server_test.go
@@ -0,0 +1,29 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package httptest
+
+import (
+ "io/ioutil"
+ "net/http"
+ "testing"
+)
+
+func TestServer(t *testing.T) {
+ ts := NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("hello"))
+ }))
+ defer ts.Close()
+ res, err := http.Get(ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(got) != "hello" {
+ t.Errorf("got %q, want hello", string(got))
+ }
+}
diff --git a/libgo/go/net/http/pprof/pprof.go b/libgo/go/net/http/pprof/pprof.go
index 21eac4743ac..0fe41b7d31b 100644
--- a/libgo/go/net/http/pprof/pprof.go
+++ b/libgo/go/net/http/pprof/pprof.go
@@ -12,15 +12,23 @@
// The handled paths all begin with /debug/pprof/.
//
// To use pprof, link this package into your program:
-// import _ "http/pprof"
+// import _ "net/http/pprof"
//
// Then use the pprof tool to look at the heap profile:
//
-// pprof http://localhost:6060/debug/pprof/heap
+// go tool pprof http://localhost:6060/debug/pprof/heap
//
// Or to look at a 30-second CPU profile:
//
-// pprof http://localhost:6060/debug/pprof/profile
+// go tool pprof http://localhost:6060/debug/pprof/profile
+//
+// Or to look at the thread creation profile:
+//
+// go tool pprof http://localhost:6060/debug/pprof/thread
+//
+// For a study of the facility in action, visit
+//
+// http://blog.golang.org/2011/06/profiling-go-programs.html
//
package pprof
@@ -43,6 +51,7 @@ func init() {
http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile))
http.Handle("/debug/pprof/heap", http.HandlerFunc(Heap))
http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol))
+ http.Handle("/debug/pprof/thread", http.HandlerFunc(Thread))
}
// Cmdline responds with the running program's
@@ -60,6 +69,13 @@ func Heap(w http.ResponseWriter, r *http.Request) {
pprof.WriteHeapProfile(w)
}
+// Thread responds with the pprof-formatted thread creation profile.
+// The package initialization registers it as /debug/pprof/thread.
+func Thread(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ pprof.WriteThreadProfile(w)
+}
+
// Profile responds with the pprof-formatted cpu profile.
// The package initialization registers it as /debug/pprof/profile.
func Profile(w http.ResponseWriter, r *http.Request) {
diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go
index e5d01698e55..165ec3624a4 100644
--- a/libgo/go/net/http/response_test.go
+++ b/libgo/go/net/http/response_test.go
@@ -321,9 +321,7 @@ func TestReadResponseCloseInMiddle(t *testing.T) {
}
if test.compressed {
buf.WriteString("Content-Encoding: gzip\r\n")
- var err error
- wr, err = gzip.NewWriter(wr)
- checkErr(err, "gzip.NewWriter")
+ wr = gzip.NewWriter(wr)
}
buf.WriteString("\r\n")
@@ -337,7 +335,7 @@ func TestReadResponseCloseInMiddle(t *testing.T) {
wr.Write(chunk)
}
if test.compressed {
- err := wr.(*gzip.Compressor).Close()
+ err := wr.(*gzip.Writer).Close()
checkErr(err, "compressor close")
}
if test.chunked {
diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go
index 288539ba576..e715c73cb6e 100644
--- a/libgo/go/net/http/server.go
+++ b/libgo/go/net/http/server.go
@@ -59,7 +59,9 @@ type ResponseWriter interface {
// Write writes the data to the connection as part of an HTTP reply.
// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
- // before writing the data.
+ // before writing the data. If the Header does not contain a
+ // Content-Type line, Write adds a Content-Type set to the result of passing
+ // the initial 512 bytes of written data to DetectContentType.
Write([]byte) (int, error)
// WriteHeader sends an HTTP response header with status code.
@@ -833,11 +835,17 @@ func RedirectHandler(url string, code int) Handler {
// redirecting any request containing . or .. elements to an
// equivalent .- and ..-free URL.
type ServeMux struct {
- m map[string]Handler
+ mu sync.RWMutex
+ m map[string]muxEntry
+}
+
+type muxEntry struct {
+ explicit bool
+ h Handler
}
// NewServeMux allocates and returns a new ServeMux.
-func NewServeMux() *ServeMux { return &ServeMux{make(map[string]Handler)} }
+func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = NewServeMux()
@@ -883,12 +891,28 @@ func (mux *ServeMux) match(path string) Handler {
}
if h == nil || len(k) > n {
n = len(k)
- h = v
+ h = v.h
}
}
return h
}
+// handler returns the handler to use for the request r.
+func (mux *ServeMux) handler(r *Request) Handler {
+ mux.mu.RLock()
+ defer mux.mu.RUnlock()
+
+ // Host-specific pattern takes precedence over generic ones
+ h := mux.match(r.Host + r.URL.Path)
+ if h == nil {
+ h = mux.match(r.URL.Path)
+ }
+ if h == nil {
+ h = NotFoundHandler()
+ }
+ return h
+}
+
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
@@ -898,30 +922,33 @@ func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
w.WriteHeader(StatusMovedPermanently)
return
}
- // Host-specific pattern takes precedence over generic ones
- h := mux.match(r.Host + r.URL.Path)
- if h == nil {
- h = mux.match(r.URL.Path)
- }
- if h == nil {
- h = NotFoundHandler()
- }
- h.ServeHTTP(w, r)
+ mux.handler(r).ServeHTTP(w, r)
}
// Handle registers the handler for the given pattern.
+// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
+ mux.mu.Lock()
+ defer mux.mu.Unlock()
+
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
+ if handler == nil {
+ panic("http: nil handler")
+ }
+ if mux.m[pattern].explicit {
+ panic("http: multiple registrations for " + pattern)
+ }
- mux.m[pattern] = handler
+ mux.m[pattern] = muxEntry{explicit: true, h: handler}
// Helpful behavior:
- // If pattern is /tree/, insert permanent redirect for /tree.
+ // If pattern is /tree/, insert an implicit permanent redirect for /tree.
+ // It can be overridden by an explicit registration.
n := len(pattern)
- if n > 0 && pattern[n-1] == '/' {
- mux.m[pattern[0:n-1]] = RedirectHandler(pattern, StatusMovedPermanently)
+ if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
+ mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(pattern, StatusMovedPermanently)}
}
}
@@ -980,15 +1007,26 @@ func (srv *Server) ListenAndServe() error {
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
+ var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
- log.Printf("http: Accept error: %v", e)
+ if tempDelay == 0 {
+ tempDelay = 5 * time.Millisecond
+ } else {
+ tempDelay *= 2
+ }
+ if max := 1 * time.Second; tempDelay > max {
+ tempDelay = max
+ }
+ log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
+ time.Sleep(tempDelay)
continue
}
return e
}
+ tempDelay = 0
if srv.ReadTimeout != 0 {
rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
}
diff --git a/libgo/go/net/http/sniff.go b/libgo/go/net/http/sniff.go
index c1c78e2417d..68f519b0542 100644
--- a/libgo/go/net/http/sniff.go
+++ b/libgo/go/net/http/sniff.go
@@ -9,15 +9,15 @@ import (
"encoding/binary"
)
-// Content-type sniffing algorithm.
-// References in this file refer to this draft specification:
-// http://mimesniff.spec.whatwg.org/
-
-// The algorithm prefers to use sniffLen bytes to make its decision.
+// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512
-// DetectContentType returns the sniffed Content-Type string
-// for the given data. This function always returns a valid MIME type.
+// DetectContentType implements the algorithm described
+// at http://mimesniff.spec.whatwg.org/ to determine the
+// Content-Type of the given data. It considers at most the
+// first 512 bytes of data. DetectContentType always returns
+// a valid MIME type: if it cannot determine a more specific one, it
+// returns "application/octet-stream".
func DetectContentType(data []byte) string {
if len(data) > sniffLen {
data = data[:sniffLen]
diff --git a/libgo/go/net/http/transport.go b/libgo/go/net/http/transport.go
index 693215edd4f..3e48abafb5e 100644
--- a/libgo/go/net/http/transport.go
+++ b/libgo/go/net/http/transport.go
@@ -85,16 +85,16 @@ func ProxyFromEnvironment(req *Request) (*url.URL, error) {
if !useProxy(canonicalAddr(req.URL)) {
return nil, nil
}
- proxyURL, err := url.ParseRequest(proxy)
+ proxyURL, err := url.Parse(proxy)
if err != nil {
- return nil, errors.New("invalid proxy address")
- }
- if proxyURL.Host == "" {
- proxyURL, err = url.ParseRequest("http://" + proxy)
- if err != nil {
- return nil, errors.New("invalid proxy address")
+ if u, err := url.Parse("http://" + proxy); err == nil {
+ proxyURL = u
+ err = nil
}
}
+ if err != nil {
+ return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
+ }
return proxyURL, nil
}
@@ -235,15 +235,19 @@ func (cm *connectMethod) proxyAuth() string {
return ""
}
-func (t *Transport) putIdleConn(pconn *persistConn) {
+// putIdleConn adds pconn to the list of idle persistent connections awaiting
+// a new request.
+// If pconn is no longer needed or not in a good state, putIdleConn
+// returns false.
+func (t *Transport) putIdleConn(pconn *persistConn) bool {
t.lk.Lock()
defer t.lk.Unlock()
if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {
pconn.close()
- return
+ return false
}
if pconn.isBroken() {
- return
+ return false
}
key := pconn.cacheKey
max := t.MaxIdleConnsPerHost
@@ -252,9 +256,10 @@ func (t *Transport) putIdleConn(pconn *persistConn) {
}
if len(t.idleConn[key]) >= max {
pconn.close()
- return
+ return false
}
t.idleConn[key] = append(t.idleConn[key], pconn)
+ return true
}
func (t *Transport) getIdleConn(cm *connectMethod) (pconn *persistConn) {
@@ -565,7 +570,9 @@ func (pc *persistConn) readLoop() {
lastbody = resp.Body
waitForBodyRead = make(chan bool)
resp.Body.(*bodyEOFSignal).fn = func() {
- pc.t.putIdleConn(pc)
+ if !pc.t.putIdleConn(pc) {
+ alive = false
+ }
waitForBodyRead <- true
}
} else {
@@ -578,7 +585,9 @@ func (pc *persistConn) readLoop() {
// read it (even though it'll just be 0, EOF).
lastbody = nil
- pc.t.putIdleConn(pc)
+ if !pc.t.putIdleConn(pc) {
+ alive = false
+ }
}
}
diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go
index caf81d6e46b..a36571a4446 100644
--- a/libgo/go/net/http/transport_test.go
+++ b/libgo/go/net/http/transport_test.go
@@ -16,6 +16,7 @@ import (
. "net/http"
"net/http/httptest"
"net/url"
+ "runtime"
"strconv"
"strings"
"testing"
@@ -441,11 +442,7 @@ func TestRoundTripGzip(t *testing.T) {
}
if accept == "gzip" {
rw.Header().Set("Content-Encoding", "gzip")
- gz, err := gzip.NewWriter(rw)
- if err != nil {
- t.Errorf("gzip NewWriter: %v", err)
- return
- }
+ gz := gzip.NewWriter(rw)
gz.Write([]byte(responseBody))
gz.Close()
} else {
@@ -512,7 +509,7 @@ func TestTransportGzip(t *testing.T) {
rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
}()
}
- gz, _ := gzip.NewWriter(w)
+ gz := gzip.NewWriter(w)
gz.Write([]byte(testString))
if req.FormValue("body") == "large" {
io.CopyN(gz, rand.Reader, nRandBytes)
@@ -636,6 +633,70 @@ func TestTransportGzipRecursive(t *testing.T) {
}
}
+// tests that persistent goroutine connections shut down when no longer desired.
+func TestTransportPersistConnLeak(t *testing.T) {
+ gotReqCh := make(chan bool)
+ unblockCh := make(chan bool)
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ gotReqCh <- true
+ <-unblockCh
+ w.Header().Set("Content-Length", "0")
+ w.WriteHeader(204)
+ }))
+ defer ts.Close()
+
+ tr := &Transport{}
+ c := &Client{Transport: tr}
+
+ n0 := runtime.Goroutines()
+
+ const numReq = 25
+ didReqCh := make(chan bool)
+ for i := 0; i < numReq; i++ {
+ go func() {
+ res, err := c.Get(ts.URL)
+ didReqCh <- true
+ if err != nil {
+ t.Errorf("client fetch error: %v", err)
+ return
+ }
+ res.Body.Close()
+ }()
+ }
+
+ // Wait for all goroutines to be stuck in the Handler.
+ for i := 0; i < numReq; i++ {
+ <-gotReqCh
+ }
+
+ nhigh := runtime.Goroutines()
+
+ // Tell all handlers to unblock and reply.
+ for i := 0; i < numReq; i++ {
+ unblockCh <- true
+ }
+
+ // Wait for all HTTP clients to be done.
+ for i := 0; i < numReq; i++ {
+ <-didReqCh
+ }
+
+ tr.CloseIdleConnections()
+ time.Sleep(100 * time.Millisecond)
+ runtime.GC()
+ runtime.GC() // even more.
+ nfinal := runtime.Goroutines()
+
+ growth := nfinal - n0
+
+ // We expect 0 or 1 extra goroutine, empirically. Allow up to 5.
+ // Previously we were leaking one per numReq.
+ t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth)
+ if int(growth) > 5 {
+ t.Error("too many new goroutines")
+ }
+}
+
type fooProto struct{}
func (fooProto) RoundTrip(req *Request) (*Response, error) {
diff --git a/libgo/go/net/http/triv.go b/libgo/go/net/http/triv.go
index 994fc0e32f6..c88a0fbce73 100644
--- a/libgo/go/net/http/triv.go
+++ b/libgo/go/net/http/triv.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 ignore
+
package main
import (