From b6724988aa748c78db3954abf59712ede0a49063 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sun, 23 Jun 2024 19:22:14 +0200 Subject: serve pre-compressed files according to accept-encoding --- internal/website/filemap.go | 53 +++++++++++++++++++++++++++++++-------------- internal/website/mux.go | 16 ++++++++++---- 2 files changed, 49 insertions(+), 20 deletions(-) (limited to 'internal') diff --git a/internal/website/filemap.go b/internal/website/filemap.go index c657848..126b67f 100644 --- a/internal/website/filemap.go +++ b/internal/website/filemap.go @@ -5,6 +5,7 @@ import ( "hash/fnv" "io" "io/fs" + "mime" "os" "path/filepath" "strings" @@ -15,11 +16,13 @@ import ( ) type File struct { - filename string - etag string + filename string + contentType string + etag string + alternatives map[string]string } -var files = map[string]File{} +var files = map[string]*File{} func hashFile(filename string) (string, error) { f, err := os.Open(filename) @@ -35,20 +38,34 @@ func hashFile(filename string) (string, error) { return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil } -func registerFile(urlpath string, filepath string) error { - if files[urlpath] != (File{}) { - log.Info("registerFile called with duplicate file", "url_path", urlpath) +var encodings = map[string]string{ + "br": ".br", + "gzip": ".gz", +} - return nil - } - hash, err := hashFile(filepath) +func registerFile(urlpath string, fp string) error { + hash, err := hashFile(fp) if err != nil { return err } - files[urlpath] = File{ - filename: filepath, - etag: hash, + f := File{ + filename: fp, + contentType: mime.TypeByExtension(filepath.Ext(fp)), + etag: hash, + alternatives: map[string]string{}, + } + for enc, suffix := range encodings { + _, err := os.Stat(fp + suffix) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } else { + return err + } + } + f.alternatives[enc] = fp + suffix } + files[urlpath] = &f return nil } @@ -62,11 +79,15 @@ func registerContentFiles(root string) error { if err != nil { return errors.WithMessagef(err, "failed to make path relative, path: %s", filePath) } - urlPath, _ := strings.CutSuffix(relPath, "index.html") + urlPath, _ := strings.CutSuffix("/"+relPath, "index.html") if !f.IsDir() { - log.Debug("registering file", "urlpath", "/"+urlPath) + switch filepath.Ext(relPath) { + case ".br", ".gz": + return nil + } + log.Debug("registering file", "urlpath", urlPath) - return registerFile("/"+urlPath, filePath) + return registerFile(urlPath, filePath) } return nil @@ -78,6 +99,6 @@ func registerContentFiles(root string) error { return nil } -func GetFile(urlPath string) File { +func GetFile(urlPath string) *File { return files[urlPath] } diff --git a/internal/website/mux.go b/internal/website/mux.go index 65a7e59..2d0a830 100644 --- a/internal/website/mux.go +++ b/internal/website/mux.go @@ -9,6 +9,7 @@ import ( "website/internal/log" "github.com/benpate/digit" + "github.com/kevinpollet/nego" "github.com/pkg/errors" ) @@ -22,7 +23,7 @@ func canonicalisePath(path string) (cPath string, differs bool) { cPath = path if strings.HasSuffix(path, "/index.html") { cPath, differs = strings.CutSuffix(path, "index.html") - } else if !strings.HasSuffix(path, "/") && files[path+"/"] != (File{}) { + } else if !strings.HasSuffix(path, "/") && files[path+"/"] != nil { cPath, differs = path+"/", true } @@ -67,7 +68,7 @@ func NewMux(cfg *config.Config, root string) (mux *http.ServeMux, err error) { return nil } file := GetFile(urlPath) - if file == (File{}) { + if file == nil { return &HTTPError{ Message: "File not found", Code: http.StatusNotFound, @@ -79,8 +80,15 @@ func NewMux(cfg *config.Config, root string) (mux *http.ServeMux, err error) { for k, v := range cfg.Extra.Headers { w.Header().Add(k, v) } - - http.ServeFile(w, r, files[urlPath].filename) + enc := nego.NegotiateContentEncoding(r, "br", "gzip") + switch enc { + case "", "identity": + http.ServeFile(w, r, files[urlPath].filename) + case "br", "gzip": + w.Header().Add("Content-Encoding", enc) + w.Header().Add("Content-Type", file.contentType) + http.ServeFile(w, r, files[urlPath].alternatives[enc]) + } return nil })) -- cgit 1.4.1