From 1fb2d56cc113d19afa3c6ccdf124ad3b452b00ef Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:48:19 -0400 Subject: [PATCH] Fixes ETag header generation and status code handling. (#35) fix: Fixes ETag header generation and status code handling. --- README.md | 1 + app.go | 1 + internal/mw/etag.go | 58 +++++++++++++++++++++++-------------- internal/server/handlers.go | 2 +- internal/server/router.go | 2 +- internal/server/server.go | 1 + internal/server/startup.go | 4 +++ 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index ad28128..d1eee44 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Flags: --banner string markdown text to be rendered at the top of the directory listing page --cors enable CORS support by setting the "Access-Control-Allow-Origin" header to "*" --disable-cache-buster disable the cache buster for assets from the directory listing feature + --disable-etag disable ETag header generation --disable-markdown disable the markdown rendering feature --ensure-unexpired-jwt enable time validation for JWT claims "exp" and "nbf" -h, --help help for http-server diff --git a/app.go b/app.go index 699a30b..9700afa 100644 --- a/app.go +++ b/app.go @@ -101,6 +101,7 @@ func run() error { flags.StringVar(&server.JWTSigningKey, "jwt-key", "", "signing key for JWT authentication") flags.BoolVar(&server.ValidateTimedJWT, "ensure-unexpired-jwt", false, "enable time validation for JWT claims \"exp\" and \"nbf\"") flags.StringVar(&server.BannerMarkdown, "banner", "", "markdown text to be rendered at the top of the directory listing page") + flags.BoolVar(&server.ETagDisabled, "disable-etag", false, "disable ETag header generation") return rootCmd.Execute() } diff --git a/internal/mw/etag.go b/internal/mw/etag.go index 8dbf052..0c4f7d8 100644 --- a/internal/mw/etag.go +++ b/internal/mw/etag.go @@ -44,31 +44,45 @@ func (e *etagResponseWriter) hasHashed() bool { } // Etag is a middleware that will calculate the ETag header for the response. -func Etag(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ew := etagResponseWriter{ - rw: w, - buf: bytes.NewBuffer(nil), - hash: sha1.New(), - } +func Etag(enabled bool) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !enabled { + next.ServeHTTP(w, r) + return + } - next.ServeHTTP(&ew, r) + ew := etagResponseWriter{ + rw: w, + buf: bytes.NewBuffer(nil), + hash: sha1.New(), + } - if !ew.hasHashed() { - w.Write(ew.buf.Bytes()) - return - } + next.ServeHTTP(&ew, r) + + if !ew.hasHashed() { + w.WriteHeader(ew.status) + w.Write(ew.buf.Bytes()) + return + } - sum := hex.EncodeToString(ew.hash.Sum(nil)) - w.Header().Set("ETag", "\""+sum+"\"") + sum := hex.EncodeToString(ew.hash.Sum(nil)) + w.Header().Set("ETag", "\""+sum+"\"") - if r.Header.Get("If-None-Match") == w.Header().Get("Etag") { - w.WriteHeader(http.StatusNotModified) - w.Write(nil) - return - } + if r.Header.Get("If-None-Match") == w.Header().Get("Etag") { + w.WriteHeader(http.StatusNotModified) + w.Write(nil) + return + } - w.WriteHeader(ew.status) - w.Write(ew.buf.Bytes()) - }) + w.WriteHeader(ew.status) + w.Write(ew.buf.Bytes()) + }) + } } + +// func Etag(next http.Handler) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// next.ServeHTTP(w, r) +// }) +// } diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 6549b66..5ca4058 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -160,7 +160,7 @@ func (s *Server) walk(requestedPath string, w http.ResponseWriter, r *http.Reque // serveFile serves a file with the appropriate headers, including support // for ETag and Last-Modified headers, as well as range requests. func (s *Server) serveFile(fp string, w http.ResponseWriter, r *http.Request) { - mw.Etag(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mw.Etag(!s.ETagDisabled)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, fp) })).ServeHTTP(w, r) } diff --git a/internal/server/router.go b/internal/server/router.go index 0457cd9..ddd9734 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -69,7 +69,7 @@ func (s *Server) router() http.Handler { // the cache buster randomized string so we can // force reload the assets on each execution assetsPrefix := path.Join(s.PathPrefix, specialPath, s.cacheBuster) - r.With(mw.Etag).HandleFunc(path.Join(assetsPrefix, "assets", "*"), s.serveAssets(assetsPrefix)) + r.With(mw.Etag(!s.ETagDisabled)).HandleFunc(path.Join(assetsPrefix, "assets", "*"), s.serveAssets(assetsPrefix)) // Create a health check endpoint r.HandleFunc(path.Join(s.PathPrefix, specialPath, "health"), s.healthCheck) diff --git a/internal/server/server.go b/internal/server/server.go index a2080ca..87627d8 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -25,6 +25,7 @@ type Server struct { // Boolean specific settings CorsEnabled bool HideLinks bool + ETagDisabled bool DisableCacheBuster bool DisableMarkdown bool MarkdownBeforeDir bool diff --git a/internal/server/startup.go b/internal/server/startup.go index f8e879c..5b4e342 100644 --- a/internal/server/startup.go +++ b/internal/server/startup.go @@ -14,6 +14,10 @@ func (s *Server) PrintStartup() { fmt.Fprintln(s.LogOutput, startupPrefix, "Path prefix:", s.PathPrefix) } + if s.ETagDisabled { + fmt.Fprintln(s.LogOutput, startupPrefix, "ETag headers disabled") + } + if s.CorsEnabled { fmt.Fprintln(s.LogOutput, startupPrefix, "CORS headers enabled: adding \"Access-Control-Allow-Origin=*\" header") }