Skip to content

Commit

Permalink
Fixes ETag header generation and status code handling. (#35)
Browse files Browse the repository at this point in the history
fix: Fixes ETag header generation and status code handling.
patrickdappollonio authored Apr 4, 2023
1 parent 098ad5c commit 1fb2d56
Showing 7 changed files with 45 additions and 24 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions app.go
Original file line number Diff line number Diff line change
@@ -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()
}
58 changes: 36 additions & 22 deletions internal/mw/etag.go
Original file line number Diff line number Diff line change
@@ -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)
// })
// }
2 changes: 1 addition & 1 deletion internal/server/handlers.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion internal/server/router.go
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ type Server struct {
// Boolean specific settings
CorsEnabled bool
HideLinks bool
ETagDisabled bool
DisableCacheBuster bool
DisableMarkdown bool
MarkdownBeforeDir bool
4 changes: 4 additions & 0 deletions internal/server/startup.go
Original file line number Diff line number Diff line change
@@ -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")
}

0 comments on commit 1fb2d56

Please sign in to comment.