From 7976f4839efd8f1de785bb24e17202dc386e5298 Mon Sep 17 00:00:00 2001 From: Bela Hullar Date: Thu, 12 Sep 2019 21:15:47 +0200 Subject: [PATCH] net/http: improve FileServer error logging The errors returned during copying the file content in ServeContent, ServeFile and FileServer were ignored to avoid excessive, meaningless logging. This commit introduces error filtering and logging to ensure that the errors occurring during reading the content are logged. Updates #27128 --- src/net/http/export_test.go | 1 + src/net/http/fs.go | 28 +++++++++++++++++++++++++++- src/net/http/fs_test.go | 22 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/net/http/export_test.go b/src/net/http/export_test.go index d265cd3f726f16..9f1baff0d8e084 100644 --- a/src/net/http/export_test.go +++ b/src/net/http/export_test.go @@ -29,6 +29,7 @@ var ( ExportErrRequestCanceledConn = errRequestCanceledConn ExportErrServerClosedIdle = errServerClosedIdle ExportServeFile = serveFile + ExportCopyNIgnoreWriteError = copyNIgnoreWriteError ExportScanETag = scanETag ExportHttp2ConfigureServer = http2ConfigureServer Export_shouldCopyHeaderOnRedirect = shouldCopyHeaderOnRedirect diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 27512411de6b71..46369f828988ac 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -295,8 +295,34 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, w.WriteHeader(code) if r.Method != "HEAD" { - io.CopyN(w, sendContent, sendSize) + if err := copyNIgnoreWriteError(w, sendContent, sendSize); err != nil { + logf(r, "http: error copy content of %s: %v", name, err) + } + } +} + +// errorSaverReader wraps an io.Reader and saves the error returned +// by the last invocation of Read in err +type errorSaverReader struct { + r io.Reader + err error +} + +func (r *errorSaverReader) Read(b []byte) (int, error) { + var n int + n, r.err = r.r.Read(b) + return n, r.err +} + +// copyNIgnoreWriteError copies n bytes (or until an error) from src to dst. +// It returns only the errors encountered during reading from src. +func copyNIgnoreWriteError(dst io.Writer, src io.Reader, n int64) error { + er := &errorSaverReader{r: src} + _, err := io.CopyN(dst, er, n) + if err == io.EOF || err == er.err { + return err } + return nil } // scanETag determines if a syntactically valid ETag is present at s. If so, diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index 047bb04ad8d947..333128cdaaab58 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -1285,6 +1285,28 @@ func (d fileServerCleanPathDir) Open(path string) (File, error) { type panicOnSeek struct{ io.ReadSeeker } +func Test_copyNIgnoreWriteError(t *testing.T) { + e := errors.New("io error") + tests := []struct { + name string + w io.Writer + r io.Reader + n int64 + wantErr error + }{ + {"short read", ioutil.Discard, new(bytes.Buffer), 1, io.EOF}, + {"read err", ioutil.Discard, errorReader{e}, 1, e}, + {"no err", ioutil.Discard, strings.NewReader("content"), 3, nil}, + {"write err", &net.IPConn{}, strings.NewReader("content"), 3, nil}, + } + for _, test := range tests { + err := ExportCopyNIgnoreWriteError(test.w, test.r, test.n) + if err != test.wantErr { + t.Errorf("%s copyNIgnoreWriteError got %v, want %v", test.name, err, test.wantErr) + } + } +} + func Test_scanETag(t *testing.T) { tests := []struct { in string