diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2012-03-02 16:38:43 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2012-03-02 16:38:43 +0000 |
commit | 2d2d80b8bd963f59534897b3d51ef8bd546cb4bc (patch) | |
tree | efa0c55763b34cbc633bc494c2743d1b5d9aaff3 /libgo/go/net/http | |
parent | 2ad2700dbf70b2e49575f3f2307839a45cf2f71c (diff) | |
download | gcc-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.go | 3 | ||||
-rw-r--r-- | libgo/go/net/http/doc.go | 4 | ||||
-rw-r--r-- | libgo/go/net/http/fcgi/child.go | 4 | ||||
-rw-r--r-- | libgo/go/net/http/fs.go | 211 | ||||
-rw-r--r-- | libgo/go/net/http/fs_test.go | 145 | ||||
-rw-r--r-- | libgo/go/net/http/httptest/server.go | 4 | ||||
-rw-r--r-- | libgo/go/net/http/httptest/server_test.go | 29 | ||||
-rw-r--r-- | libgo/go/net/http/pprof/pprof.go | 22 | ||||
-rw-r--r-- | libgo/go/net/http/response_test.go | 6 | ||||
-rw-r--r-- | libgo/go/net/http/server.go | 74 | ||||
-rw-r--r-- | libgo/go/net/http/sniff.go | 14 | ||||
-rw-r--r-- | libgo/go/net/http/transport.go | 35 | ||||
-rw-r--r-- | libgo/go/net/http/transport_test.go | 73 | ||||
-rw-r--r-- | libgo/go/net/http/triv.go | 2 |
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 ( |