diff options
author | Ian Lance Taylor <iant@google.com> | 2014-06-04 23:15:33 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2014-06-04 23:15:33 +0000 |
commit | bae90c989cb020d17a24919ec84c0b8dd2fae2da (patch) | |
tree | 89766166feb4ceca2d983169c5360e3f6f521b12 /libgo/go/net/http | |
parent | 82b3da6a714493644a4333bfd8205e3341ed3b8e (diff) | |
download | gcc-bae90c989cb020d17a24919ec84c0b8dd2fae2da.tar.gz |
libgo: Merge from revision 18783:00cce3a34d7e of master library.
This revision was committed January 7, 2014. The next
revision deleted runtime/mfinal.c. That will be done in a
subsequent merge.
This merge changes type descriptors to add a zero field,
pointing to a zero value for that type. This is implemented
as a common variable.
* go-gcc.cc (Gcc_backend::implicit_variable): Add is_common and
alignment parameters. Permit init parameter to be NULL.
From-SVN: r211249
Diffstat (limited to 'libgo/go/net/http')
-rw-r--r-- | libgo/go/net/http/client_test.go | 20 | ||||
-rw-r--r-- | libgo/go/net/http/cookie.go | 2 | ||||
-rw-r--r-- | libgo/go/net/http/cookie_test.go | 28 | ||||
-rw-r--r-- | libgo/go/net/http/cookiejar/jar.go | 6 | ||||
-rw-r--r-- | libgo/go/net/http/header.go | 17 | ||||
-rw-r--r-- | libgo/go/net/http/request.go | 27 | ||||
-rw-r--r-- | libgo/go/net/http/request_test.go | 41 | ||||
-rw-r--r-- | libgo/go/net/http/response.go | 1 | ||||
-rw-r--r-- | libgo/go/net/http/response_test.go | 7 | ||||
-rw-r--r-- | libgo/go/net/http/serve_test.go | 108 | ||||
-rw-r--r-- | libgo/go/net/http/server.go | 57 | ||||
-rw-r--r-- | libgo/go/net/http/transfer.go | 41 | ||||
-rw-r--r-- | libgo/go/net/http/transfer_test.go | 6 | ||||
-rw-r--r-- | libgo/go/net/http/transport_test.go | 2 |
14 files changed, 266 insertions, 97 deletions
diff --git a/libgo/go/net/http/client_test.go b/libgo/go/net/http/client_test.go index 997d04151c2..e5ad39c7741 100644 --- a/libgo/go/net/http/client_test.go +++ b/libgo/go/net/http/client_test.go @@ -373,24 +373,6 @@ func (j *TestJar) Cookies(u *url.URL) []*Cookie { return j.perURL[u.Host] } -func TestRedirectCookiesOnRequest(t *testing.T) { - defer afterTest(t) - var ts *httptest.Server - ts = httptest.NewServer(echoCookiesRedirectHandler) - defer ts.Close() - c := &Client{} - req, _ := NewRequest("GET", ts.URL, nil) - req.AddCookie(expectedCookies[0]) - // TODO: Uncomment when an implementation of a RFC6265 cookie jar lands. - _ = c - // resp, _ := c.Do(req) - // matchReturnedCookies(t, expectedCookies, resp.Cookies()) - - req, _ = NewRequest("GET", ts.URL, nil) - // resp, _ = c.Do(req) - // matchReturnedCookies(t, expectedCookies[1:], resp.Cookies()) -} - func TestRedirectCookiesJar(t *testing.T) { defer afterTest(t) var ts *httptest.Server @@ -410,8 +392,8 @@ func TestRedirectCookiesJar(t *testing.T) { } func matchReturnedCookies(t *testing.T, expected, given []*Cookie) { - t.Logf("Received cookies: %v", given) if len(given) != len(expected) { + t.Logf("Received cookies: %v", given) t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) } for _, ec := range expected { diff --git a/libgo/go/net/http/cookie.go b/libgo/go/net/http/cookie.go index 8b01c508eb1..a1759214f38 100644 --- a/libgo/go/net/http/cookie.go +++ b/libgo/go/net/http/cookie.go @@ -94,7 +94,6 @@ func readSetCookies(h Header) []*Cookie { continue case "domain": c.Domain = val - // TODO: Add domain parsing continue case "max-age": secs, err := strconv.Atoi(val) @@ -121,7 +120,6 @@ func readSetCookies(h Header) []*Cookie { continue case "path": c.Path = val - // TODO: Add path parsing continue } c.Unparsed = append(c.Unparsed, parts[i]) diff --git a/libgo/go/net/http/cookie_test.go b/libgo/go/net/http/cookie_test.go index 11b01cc5713..1aa9d49d96e 100644 --- a/libgo/go/net/http/cookie_test.go +++ b/libgo/go/net/http/cookie_test.go @@ -5,9 +5,13 @@ package http import ( + "bytes" "encoding/json" "fmt" + "log" + "os" "reflect" + "strings" "testing" "time" ) @@ -51,12 +55,20 @@ var writeSetCookiesTests = []struct { } func TestWriteSetCookies(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + for i, tt := range writeSetCookiesTests { if g, e := tt.Cookie.String(), tt.Raw; g != e { t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g) continue } } + + if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } type headerOnlyResponseWriter Header @@ -244,6 +256,10 @@ func TestReadCookies(t *testing.T) { } func TestCookieSanitizeValue(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + tests := []struct { in, want string }{ @@ -257,9 +273,17 @@ func TestCookieSanitizeValue(t *testing.T) { t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want) } } + + if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } func TestCookieSanitizePath(t *testing.T) { + defer log.SetOutput(os.Stderr) + var logbuf bytes.Buffer + log.SetOutput(&logbuf) + tests := []struct { in, want string }{ @@ -272,4 +296,8 @@ func TestCookieSanitizePath(t *testing.T) { t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want) } } + + if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { + t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) + } } diff --git a/libgo/go/net/http/cookiejar/jar.go b/libgo/go/net/http/cookiejar/jar.go index 389ab58e418..82f18a17848 100644 --- a/libgo/go/net/http/cookiejar/jar.go +++ b/libgo/go/net/http/cookiejar/jar.go @@ -34,9 +34,9 @@ import ( type PublicSuffixList interface { // PublicSuffix returns the public suffix of domain. // - // TODO: specify which of the caller and callee is responsible for IP - // addresses, for leading and trailing dots, for case sensitivity, and - // for IDN/Punycode. + // Domain is a lowercase punycoded domain name (not an IP address) + // without leading or trailing dots. The returned value is in the + // same form. PublicSuffix(domain string) string // String returns a description of the source of this public suffix diff --git a/libgo/go/net/http/header.go b/libgo/go/net/http/header.go index ca1ae07c25d..de62bef5525 100644 --- a/libgo/go/net/http/header.go +++ b/libgo/go/net/http/header.go @@ -9,6 +9,7 @@ import ( "net/textproto" "sort" "strings" + "sync" "time" ) @@ -114,18 +115,15 @@ func (s *headerSorter) Len() int { return len(s.kvs) } func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } -// TODO: convert this to a sync.Cache (issue 4720) -var headerSorterCache = make(chan *headerSorter, 8) +var headerSorterPool = sync.Pool{ + New: func() interface{} { return new(headerSorter) }, +} // sortedKeyValues returns h's keys sorted in the returned kvs // slice. The headerSorter used to sort is also returned, for possible // return to headerSorterCache. func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { - select { - case hs = <-headerSorterCache: - default: - hs = new(headerSorter) - } + hs = headerSorterPool.Get().(*headerSorter) if cap(hs.kvs) < len(h) { hs.kvs = make([]keyValues, 0, len(h)) } @@ -159,10 +157,7 @@ func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { } } } - select { - case headerSorterCache <- sorter: - default: - } + headerSorterPool.Put(sorter) return nil } diff --git a/libgo/go/net/http/request.go b/libgo/go/net/http/request.go index 57b5d094847..7a97770314d 100644 --- a/libgo/go/net/http/request.go +++ b/libgo/go/net/http/request.go @@ -20,6 +20,7 @@ import ( "net/url" "strconv" "strings" + "sync" ) const ( @@ -494,25 +495,20 @@ func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { return line[:s1], line[s1+1 : s2], line[s2+1:], true } -// TODO(bradfitz): use a sync.Cache when available -var textprotoReaderCache = make(chan *textproto.Reader, 4) +var textprotoReaderPool sync.Pool func newTextprotoReader(br *bufio.Reader) *textproto.Reader { - select { - case r := <-textprotoReaderCache: - r.R = br - return r - default: - return textproto.NewReader(br) + if v := textprotoReaderPool.Get(); v != nil { + tr := v.(*textproto.Reader) + tr.R = br + return tr } + return textproto.NewReader(br) } func putTextprotoReader(r *textproto.Reader) { r.R = nil - select { - case textprotoReaderCache <- r: - default: - } + textprotoReaderPool.Put(r) } // ReadRequest reads and parses a request from b. @@ -677,6 +673,11 @@ func parsePostForm(r *Request) (vs url.Values, err error) { return } ct := r.Header.Get("Content-Type") + // RFC 2616, section 7.2.1 - empty type + // SHOULD be treated as application/octet-stream + if ct == "" { + ct = "application/octet-stream" + } ct, _, err = mime.ParseMediaType(ct) switch { case ct == "application/x-www-form-urlencoded": @@ -707,7 +708,7 @@ func parsePostForm(r *Request) (vs url.Values, err error) { // orders to call too many functions here. // Clean this up and write more tests. // request_test.go contains the start of this, - // in TestRequestMultipartCallOrder. + // in TestParseMultipartFormOrder and others. } return } diff --git a/libgo/go/net/http/request_test.go b/libgo/go/net/http/request_test.go index 89303c33602..0c1e16b8d5e 100644 --- a/libgo/go/net/http/request_test.go +++ b/libgo/go/net/http/request_test.go @@ -68,8 +68,9 @@ type parseContentTypeTest struct { var parseContentTypeTests = []parseContentTypeTest{ {false, stringMap{"Content-Type": {"text/plain"}}}, - // Non-existent keys are not placed. The value nil is illegal. - {true, stringMap{}}, + // Empty content type is legal - shoult be treated as + // application/octet-stream (RFC 2616, section 7.2.1) + {false, stringMap{}}, {true, stringMap{"Content-Type": {"text/plain; boundary="}}}, {false, stringMap{"Content-Type": {"application/unknown"}}}, } @@ -198,15 +199,39 @@ func TestEmptyMultipartRequest(t *testing.T) { testMissingFile(t, req) } -func TestRequestMultipartCallOrder(t *testing.T) { +// Test that ParseMultipartForm errors if called +// after MultipartReader on the same request. +func TestParseMultipartFormOrder(t *testing.T) { req := newTestMultipartRequest(t) - _, err := req.MultipartReader() - if err != nil { + if _, err := req.MultipartReader(); err != nil { + t.Fatalf("MultipartReader: %v", err) + } + if err := req.ParseMultipartForm(1024); err == nil { + t.Fatal("expected an error from ParseMultipartForm after call to MultipartReader") + } +} + +// Test that MultipartReader errors if called +// after ParseMultipartForm on the same request. +func TestMultipartReaderOrder(t *testing.T) { + req := newTestMultipartRequest(t) + if err := req.ParseMultipartForm(25); err != nil { + t.Fatalf("ParseMultipartForm: %v", err) + } + if _, err := req.MultipartReader(); err == nil { + t.Fatal("expected an error from MultipartReader after call to ParseMultipartForm") + } +} + +// Test that FormFile errors if called after +// MultipartReader on the same request. +func TestFormFileOrder(t *testing.T) { + req := newTestMultipartRequest(t) + if _, err := req.MultipartReader(); err != nil { t.Fatalf("MultipartReader: %v", err) } - err = req.ParseMultipartForm(1024) - if err == nil { - t.Errorf("expected an error from ParseMultipartForm after call to MultipartReader") + if _, _, err := req.FormFile(""); err == nil { + t.Fatal("expected an error from FormFile after call to MultipartReader") } } diff --git a/libgo/go/net/http/response.go b/libgo/go/net/http/response.go index 35d0ba3bb15..2ec1d408728 100644 --- a/libgo/go/net/http/response.go +++ b/libgo/go/net/http/response.go @@ -187,6 +187,7 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // ContentLength // Header, values for non-canonical keys will have unpredictable behavior // +// Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { // Status line diff --git a/libgo/go/net/http/response_test.go b/libgo/go/net/http/response_test.go index 5044306a876..f73172189e9 100644 --- a/libgo/go/net/http/response_test.go +++ b/libgo/go/net/http/response_test.go @@ -14,6 +14,7 @@ import ( "io/ioutil" "net/url" "reflect" + "regexp" "strings" "testing" ) @@ -406,8 +407,7 @@ func TestWriteResponse(t *testing.T) { t.Errorf("#%d: %v", i, err) continue } - bout := bytes.NewBuffer(nil) - err = resp.Write(bout) + err = resp.Write(ioutil.Discard) if err != nil { t.Errorf("#%d: %v", i, err) continue @@ -506,6 +506,9 @@ func TestReadResponseCloseInMiddle(t *testing.T) { rest, err := ioutil.ReadAll(bufr) checkErr(err, "ReadAll on remainder") if e, g := "Next Request Here", string(rest); e != g { + g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { + return fmt.Sprintf("x(repeated x%d)", len(match)) + }) fatalf("remainder = %q, expected %q", g, e) } } diff --git a/libgo/go/net/http/serve_test.go b/libgo/go/net/http/serve_test.go index 8961cf491f8..1dba1873ea5 100644 --- a/libgo/go/net/http/serve_test.go +++ b/libgo/go/net/http/serve_test.go @@ -1934,6 +1934,31 @@ func TestWriteAfterHijack(t *testing.T) { } } +func TestDoubleHijack(t *testing.T) { + req := reqBytes("GET / HTTP/1.1\nHost: golang.org") + var buf bytes.Buffer + conn := &rwTestConn{ + Reader: bytes.NewReader(req), + Writer: &buf, + closec: make(chan bool, 1), + } + handler := HandlerFunc(func(rw ResponseWriter, r *Request) { + conn, _, err := rw.(Hijacker).Hijack() + if err != nil { + t.Error(err) + return + } + _, _, err = rw.(Hijacker).Hijack() + if err == nil { + t.Errorf("got err = nil; want err != nil") + } + conn.Close() + }) + ln := &oneConnListener{conn: conn} + go Serve(ln, handler) + <-conn.closec +} + // http://code.google.com/p/go/issues/detail?id=5955 // Note that this does not test the "request too large" // exit path from the http server. This is intentional; @@ -2065,6 +2090,64 @@ func TestNoContentTypeOnNotModified(t *testing.T) { } } +// Issue 6995 +// A server Handler can receive a Request, and then turn around and +// give a copy of that Request.Body out to the Transport (e.g. any +// proxy). So then two people own that Request.Body (both the server +// and the http client), and both think they can close it on failure. +// Therefore, all incoming server requests Bodies need to be thread-safe. +func TestTransportAndServerSharedBodyRace(t *testing.T) { + defer afterTest(t) + + const bodySize = 1 << 20 + + unblockBackend := make(chan bool) + backend := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + io.CopyN(rw, req.Body, bodySize/2) + <-unblockBackend + })) + defer backend.Close() + + backendRespc := make(chan *Response, 1) + proxy := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + if req.RequestURI == "/foo" { + rw.Write([]byte("bar")) + return + } + req2, _ := NewRequest("POST", backend.URL, req.Body) + req2.ContentLength = bodySize + + bresp, err := DefaultClient.Do(req2) + if err != nil { + t.Errorf("Proxy outbound request: %v", err) + return + } + _, err = io.CopyN(ioutil.Discard, bresp.Body, bodySize/4) + if err != nil { + t.Errorf("Proxy copy error: %v", err) + return + } + backendRespc <- bresp // to close later + + // Try to cause a race: Both the DefaultTransport and the proxy handler's Server + // will try to read/close req.Body (aka req2.Body) + DefaultTransport.(*Transport).CancelRequest(req2) + rw.Write([]byte("OK")) + })) + defer proxy.Close() + + req, _ := NewRequest("POST", proxy.URL, io.LimitReader(neverEnding('a'), bodySize)) + res, err := DefaultClient.Do(req) + if err != nil { + t.Fatalf("Original request: %v", err) + } + + // Cleanup, so we don't leak goroutines. + res.Body.Close() + close(unblockBackend) + (<-backendRespc).Body.Close() +} + func TestResponseWriterWriteStringAllocs(t *testing.T) { t.Skip("allocs test unreliable with gccgo") ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { @@ -2391,3 +2474,28 @@ Host: golang.org b.Errorf("b.N=%d but handled %d", b.N, handled) } } + +func BenchmarkServerHijack(b *testing.B) { + b.ReportAllocs() + req := reqBytes(`GET / HTTP/1.1 +Host: golang.org +`) + h := HandlerFunc(func(w ResponseWriter, r *Request) { + conn, _, err := w.(Hijacker).Hijack() + if err != nil { + panic(err) + } + conn.Close() + }) + conn := &rwTestConn{ + Writer: ioutil.Discard, + closec: make(chan bool, 1), + } + ln := &oneConnListener{conn: conn} + for i := 0; i < b.N; i++ { + conn.Reader = bytes.NewReader(req) + ln.conn = conn + Serve(ln, h) + <-conn.closec + } +} diff --git a/libgo/go/net/http/server.go b/libgo/go/net/http/server.go index 0e46863d5ae..7ebd8575f3b 100644 --- a/libgo/go/net/http/server.go +++ b/libgo/go/net/http/server.go @@ -435,56 +435,52 @@ func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { return c, nil } -// TODO: use a sync.Cache instead var ( - bufioReaderCache = make(chan *bufio.Reader, 4) - bufioWriterCache2k = make(chan *bufio.Writer, 4) - bufioWriterCache4k = make(chan *bufio.Writer, 4) + bufioReaderPool sync.Pool + bufioWriter2kPool sync.Pool + bufioWriter4kPool sync.Pool ) -func bufioWriterCache(size int) chan *bufio.Writer { +func bufioWriterPool(size int) *sync.Pool { switch size { case 2 << 10: - return bufioWriterCache2k + return &bufioWriter2kPool case 4 << 10: - return bufioWriterCache4k + return &bufioWriter4kPool } return nil } func newBufioReader(r io.Reader) *bufio.Reader { - select { - case p := <-bufioReaderCache: - p.Reset(r) - return p - default: - return bufio.NewReader(r) + if v := bufioReaderPool.Get(); v != nil { + br := v.(*bufio.Reader) + br.Reset(r) + return br } + return bufio.NewReader(r) } func putBufioReader(br *bufio.Reader) { br.Reset(nil) - select { - case bufioReaderCache <- br: - default: - } + bufioReaderPool.Put(br) } func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { - select { - case p := <-bufioWriterCache(size): - p.Reset(w) - return p - default: - return bufio.NewWriterSize(w, size) + pool := bufioWriterPool(size) + if pool != nil { + if v := pool.Get(); v != nil { + bw := v.(*bufio.Writer) + bw.Reset(w) + return bw + } } + return bufio.NewWriterSize(w, size) } func putBufioWriter(bw *bufio.Writer) { bw.Reset(nil) - select { - case bufioWriterCache(bw.Available()) <- bw: - default: + if pool := bufioWriterPool(bw.Available()); pool != nil { + pool.Put(bw) } } @@ -1202,7 +1198,14 @@ func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { if w.wroteHeader { w.cw.flush() } - return w.conn.hijack() + // Release the bufioWriter that writes to the chunk writer, it is not + // used after a connection has been hijacked. + rwc, buf, err = w.conn.hijack() + if err == nil { + putBufioWriter(w.w) + w.w = nil + } + return rwc, buf, err } func (w *response) CloseNotify() <-chan bool { diff --git a/libgo/go/net/http/transfer.go b/libgo/go/net/http/transfer.go index bacd83732de..4a2bda19fac 100644 --- a/libgo/go/net/http/transfer.go +++ b/libgo/go/net/http/transfer.go @@ -14,6 +14,7 @@ import ( "net/textproto" "strconv" "strings" + "sync" ) // transferWriter inspects the fields of a user-supplied Request or Response, @@ -331,17 +332,17 @@ func readTransfer(msg interface{}, r *bufio.Reader) (err error) { if noBodyExpected(t.RequestMethod) { t.Body = eofReader } else { - t.Body = &body{Reader: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} + t.Body = &body{src: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} } case realLength == 0: t.Body = eofReader case realLength > 0: - t.Body = &body{Reader: io.LimitReader(r, realLength), closing: t.Close} + t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close} default: // realLength < 0, i.e. "Content-Length" not mentioned in header if t.Close { // Close semantics (i.e. HTTP/1.0) - t.Body = &body{Reader: r, closing: t.Close} + t.Body = &body{src: r, closing: t.Close} } else { // Persistent connection (i.e. HTTP/1.1) t.Body = eofReader @@ -514,11 +515,13 @@ func fixTrailer(header Header, te []string) (Header, error) { // Close ensures that the body has been fully read // and then reads the trailer if necessary. type body struct { - io.Reader + src io.Reader hdr interface{} // non-nil (Response or Request) value means read trailer r *bufio.Reader // underlying wire-format reader for the trailer closing bool // is the connection to be closed after reading body? - closed bool + + mu sync.Mutex // guards closed, and calls to Read and Close + closed bool } // ErrBodyReadAfterClose is returned when reading a Request or Response @@ -528,10 +531,17 @@ type body struct { var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body") func (b *body) Read(p []byte) (n int, err error) { + b.mu.Lock() + defer b.mu.Unlock() if b.closed { return 0, ErrBodyReadAfterClose } - n, err = b.Reader.Read(p) + return b.readLocked(p) +} + +// Must hold b.mu. +func (b *body) readLocked(p []byte) (n int, err error) { + n, err = b.src.Read(p) if err == io.EOF { // Chunked case. Read the trailer. @@ -543,7 +553,7 @@ func (b *body) Read(p []byte) (n int, err error) { } else { // If the server declared the Content-Length, our body is a LimitedReader // and we need to check whether this EOF arrived early. - if lr, ok := b.Reader.(*io.LimitedReader); ok && lr.N > 0 { + if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > 0 { err = io.ErrUnexpectedEOF } } @@ -618,6 +628,8 @@ func (b *body) readTrailer() error { } func (b *body) Close() error { + b.mu.Lock() + defer b.mu.Unlock() if b.closed { return nil } @@ -629,12 +641,25 @@ func (b *body) Close() error { default: // Fully consume the body, which will also lead to us reading // the trailer headers after the body, if present. - _, err = io.Copy(ioutil.Discard, b) + _, err = io.Copy(ioutil.Discard, bodyLocked{b}) } b.closed = true return err } +// bodyLocked is a io.Reader reading from a *body when its mutex is +// already held. +type bodyLocked struct { + b *body +} + +func (bl bodyLocked) Read(p []byte) (n int, err error) { + if bl.b.closed { + return 0, ErrBodyReadAfterClose + } + return bl.b.readLocked(p) +} + // parseContentLength trims whitespace from s and returns -1 if no value // is set, or the value if it's >= 0. func parseContentLength(cl string) (int64, error) { diff --git a/libgo/go/net/http/transfer_test.go b/libgo/go/net/http/transfer_test.go index 8627a374c8f..fb5ef37a0f0 100644 --- a/libgo/go/net/http/transfer_test.go +++ b/libgo/go/net/http/transfer_test.go @@ -12,9 +12,9 @@ import ( func TestBodyReadBadTrailer(t *testing.T) { b := &body{ - Reader: strings.NewReader("foobar"), - hdr: true, // force reading the trailer - r: bufio.NewReader(strings.NewReader("")), + src: strings.NewReader("foobar"), + hdr: true, // force reading the trailer + r: bufio.NewReader(strings.NewReader("")), } buf := make([]byte, 7) n, err := b.Read(buf[:3]) diff --git a/libgo/go/net/http/transport_test.go b/libgo/go/net/http/transport_test.go index e4df30a98de..2ce2b6b5180 100644 --- a/libgo/go/net/http/transport_test.go +++ b/libgo/go/net/http/transport_test.go @@ -798,8 +798,8 @@ func TestTransportPersistConnLeak(t *testing.T) { // 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.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth) t.Error("too many new goroutines") } } |