From a853984f52dbdcd953fec9c3ae055bc8cd1898fc Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 22 Dec 2022 10:25:30 +0800 Subject: [PATCH] fix issue #2334 when accessing static files with cache time (#2366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Solve the problem of error when accessing static files with cache time. Error message: 2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58) Stack: Verification method: curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed * Solve the problem of error when accessing static files with cache time. Error message: 2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58) Stack: Verification method: curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed * Solve the problem of error when accessing static files with cache time. Error message: 2022-11-29 19:40:11.090 [ERRO] http: superfluous response.WriteHeader call from github.com/gogf/gf/v2/net/ghttp.(*ResponseWriter).Flush (ghttp_response_writer.go:58) Stack: Verification method: curl 'http://127.0.0.1:8000/' -H 'If-Modified-Since: Thu, 08 Dec 2022 03:13:55 GMT' --compressed * fix issue #2334 when accessing static files with cache time * up Co-authored-by: 曾洪亮 Co-authored-by: houseme --- net/ghttp/ghttp_response.go | 14 ++++++++++++++ net/ghttp/ghttp_response_writer.go | 14 ++++++++++---- net/ghttp/ghttp_server_handler.go | 4 ++-- net/ghttp/ghttp_z_unit_issue_test.go | 22 +++++++++++++++++++--- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/net/ghttp/ghttp_response.go b/net/ghttp/ghttp_response.go index b7011a259..02596693e 100644 --- a/net/ghttp/ghttp_response.go +++ b/net/ghttp/ghttp_response.go @@ -10,8 +10,10 @@ package ghttp import ( "bytes" "fmt" + "io" "net/http" "net/url" + "time" "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/os/gfile" @@ -142,6 +144,18 @@ func (r *Response) ClearBuffer() { r.buffer.Reset() } +// 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-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, +// and If-Range requests. +// +// See http.ServeContent +func (r *Response) ServeContent(name string, modTime time.Time, content io.ReadSeeker) { + r.wroteHeader = true + http.ServeContent(r.Writer.RawWriter(), r.Request.Request, name, modTime, content) +} + // Flush outputs the buffer content to the client and clears the buffer. func (r *Response) Flush() { r.Header().Set(responseHeaderTraceID, gtrace.GetTraceID(r.Request.Context())) diff --git a/net/ghttp/ghttp_response_writer.go b/net/ghttp/ghttp_response_writer.go index dd708acee..948cb9974 100644 --- a/net/ghttp/ghttp_response_writer.go +++ b/net/ghttp/ghttp_response_writer.go @@ -16,10 +16,11 @@ import ( // ResponseWriter is the custom writer for http response. type ResponseWriter struct { - Status int // HTTP status. - writer http.ResponseWriter // The underlying ResponseWriter. - buffer *bytes.Buffer // The output buffer. - hijacked bool // Mark this request is hijacked or not. + Status int // HTTP status. + writer http.ResponseWriter // The underlying ResponseWriter. + buffer *bytes.Buffer // The output buffer. + hijacked bool // Mark this request is hijacked or not. + wroteHeader bool // Is header wrote or not, avoiding error: superfluous/multiple response.WriteHeader call. } // RawWriter returns the underlying ResponseWriter. @@ -54,7 +55,9 @@ func (w *ResponseWriter) Flush() { if w.hijacked { return } + if w.Status != 0 && !w.isHeaderWritten() { + w.wroteHeader = true w.writer.WriteHeader(w.Status) } // Default status text output. @@ -69,6 +72,9 @@ func (w *ResponseWriter) Flush() { // isHeaderWrote checks and returns whether the header is written. func (w *ResponseWriter) isHeaderWritten() bool { + if w.wroteHeader { + return true + } if _, ok := w.writer.Header()[responseHeaderContentLength]; ok { return true } diff --git a/net/ghttp/ghttp_server_handler.go b/net/ghttp/ghttp_server_handler.go index 142c3df7c..91d6802d9 100644 --- a/net/ghttp/ghttp_server_handler.go +++ b/net/ghttp/ghttp_server_handler.go @@ -276,7 +276,7 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) { } } else { info := f.File.FileInfo() - http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), f.File) + r.Response.ServeContent(info.Name(), info.ModTime(), f.File) } return } @@ -300,7 +300,7 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) { r.Response.WriteStatus(http.StatusForbidden) } } else { - http.ServeContent(r.Response.Writer.RawWriter(), r.Request, info.Name(), info.ModTime(), file) + r.Response.ServeContent(info.Name(), info.ModTime(), file) } } diff --git a/net/ghttp/ghttp_z_unit_issue_test.go b/net/ghttp/ghttp_z_unit_issue_test.go index 0eefcef8b..fd7fb0ab2 100644 --- a/net/ghttp/ghttp_z_unit_issue_test.go +++ b/net/ghttp/ghttp_z_unit_issue_test.go @@ -131,7 +131,6 @@ func Test_Issue1653(t *testing.T) { s.Group("/boot", func(grp *ghttp.RouterGroup) { grp.Bind(Issue1653Foo) }) - s.SetPort(9527) s.SetDumpRouterMap(false) s.Start() defer s.Shutdown() @@ -207,7 +206,6 @@ func Test_Issue662(t *testing.T) { s.Group("/boot", func(grp *ghttp.RouterGroup) { grp.Bind(Foo1) }) - s.SetPort(8888) s.SetDumpRouterMap(false) s.Start() defer s.Shutdown() @@ -251,7 +249,6 @@ func Test_Issue2172(t *testing.T) { s.Group("/", func(group *ghttp.RouterGroup) { group.Bind(api) }) - s.SetPort(8888) s.SetDumpRouterMap(false) s.Start() defer s.Shutdown() @@ -264,3 +261,22 @@ func Test_Issue2172(t *testing.T) { t.Assert(c.PostContent(ctx, "/demo", dataReq), `{"code":0,"message":"","data":{"Content":"{\"asd\":1}"}}`) }) } + +// https://github.com/gogf/gf/issues/2334 +func Test_Issue2334(t *testing.T) { + s := g.Server(guid.S()) + s.SetServerRoot(gtest.DataPath("static1")) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + time.Sleep(1000 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c := g.Client() + c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) + t.Assert(c.GetContent(ctx, "/index.html"), "index") + + c.SetHeader("If-Modified-Since", "Mon, 12 Dec 2040 05:53:35 GMT") + res, _ := c.Get(ctx, "/index.html") + t.Assert(res.StatusCode, 304) + }) +}