Skip to content

Commit de04bf2

Browse files
committed
net/http: fix early side effects in the ResponseWriter's ReadFrom
The ResponseWriter's ReadFrom method was causing side effects on the output before any data was read. Now, bail out early and do a normal copy (which does a read before writing) when our input and output are known to not to be the pair of types we need for sendfile. Fixes #5660 R=golang-dev, rsc, nightlyone CC=golang-dev https://golang.org/cl/12632043
1 parent 390656a commit de04bf2

File tree

2 files changed

+83
-8
lines changed

2 files changed

+83
-8
lines changed

src/pkg/net/http/serve_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,51 @@ func TestHTTP10ConnectionHeader(t *testing.T) {
18151815
}
18161816
}
18171817

1818+
// See golang.org/issue/5660
1819+
func TestServerReaderFromOrder(t *testing.T) {
1820+
defer afterTest(t)
1821+
pr, pw := io.Pipe()
1822+
const size = 3 << 20
1823+
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
1824+
rw.Header().Set("Content-Type", "text/plain") // prevent sniffing path
1825+
done := make(chan bool)
1826+
go func() {
1827+
io.Copy(rw, pr)
1828+
close(done)
1829+
}()
1830+
time.Sleep(25 * time.Millisecond) // give Copy a chance to break things
1831+
n, err := io.Copy(ioutil.Discard, req.Body)
1832+
if err != nil {
1833+
t.Errorf("handler Copy: %v", err)
1834+
return
1835+
}
1836+
if n != size {
1837+
t.Errorf("handler Copy = %d; want %d", n, size)
1838+
}
1839+
pw.Write([]byte("hi"))
1840+
pw.Close()
1841+
<-done
1842+
}))
1843+
defer ts.Close()
1844+
1845+
req, err := NewRequest("POST", ts.URL, io.LimitReader(neverEnding('a'), size))
1846+
if err != nil {
1847+
t.Fatal(err)
1848+
}
1849+
res, err := DefaultClient.Do(req)
1850+
if err != nil {
1851+
t.Fatal(err)
1852+
}
1853+
all, err := ioutil.ReadAll(res.Body)
1854+
if err != nil {
1855+
t.Fatal(err)
1856+
}
1857+
res.Body.Close()
1858+
if string(all) != "hi" {
1859+
t.Errorf("Body = %q; want hi", all)
1860+
}
1861+
}
1862+
18181863
func BenchmarkClientServer(b *testing.B) {
18191864
b.ReportAllocs()
18201865
b.StopTimer()

src/pkg/net/http/server.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"log"
1717
"net"
1818
"net/url"
19+
"os"
1920
"path"
2021
"runtime"
2122
"strconv"
@@ -345,11 +346,44 @@ func (w *response) needsSniff() bool {
345346
return !w.cw.wroteHeader && w.handlerHeader.Get("Content-Type") == "" && w.written < sniffLen
346347
}
347348

349+
// writerOnly hides an io.Writer value's optional ReadFrom method
350+
// from io.Copy.
348351
type writerOnly struct {
349352
io.Writer
350353
}
351354

355+
func srcIsRegularFile(src io.Reader) (isRegular bool, err error) {
356+
switch v := src.(type) {
357+
case *os.File:
358+
fi, err := v.Stat()
359+
if err != nil {
360+
return false, err
361+
}
362+
return fi.Mode().IsRegular(), nil
363+
case *io.LimitedReader:
364+
return srcIsRegularFile(v.R)
365+
default:
366+
return
367+
}
368+
}
369+
370+
// ReadFrom is here to optimize copying from an *os.File regular file
371+
// to a *net.TCPConn with sendfile.
352372
func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
373+
// Our underlying w.conn.rwc is usually a *TCPConn (with its
374+
// own ReadFrom method). If not, or if our src isn't a regular
375+
// file, just fall back to the normal copy method.
376+
rf, ok := w.conn.rwc.(io.ReaderFrom)
377+
regFile, err := srcIsRegularFile(src)
378+
if err != nil {
379+
return 0, err
380+
}
381+
if !ok || !regFile {
382+
return io.Copy(writerOnly{w}, src)
383+
}
384+
385+
// sendfile path:
386+
353387
if !w.wroteHeader {
354388
w.WriteHeader(StatusOK)
355389
}
@@ -367,16 +401,12 @@ func (w *response) ReadFrom(src io.Reader) (n int64, err error) {
367401

368402
// Now that cw has been flushed, its chunking field is guaranteed initialized.
369403
if !w.cw.chunking && w.bodyAllowed() {
370-
if rf, ok := w.conn.rwc.(io.ReaderFrom); ok {
371-
n0, err := rf.ReadFrom(src)
372-
n += n0
373-
w.written += n0
374-
return n, err
375-
}
404+
n0, err := rf.ReadFrom(src)
405+
n += n0
406+
w.written += n0
407+
return n, err
376408
}
377409

378-
// Fall back to default io.Copy implementation.
379-
// Use wrapper to hide w.ReadFrom from io.Copy.
380410
n0, err := io.Copy(writerOnly{w}, src)
381411
n += n0
382412
return n, err

0 commit comments

Comments
 (0)