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 --- go.mod | 3 ++- go.sum | 6 +++-- internal/website/filemap.go | 53 +++++++++++++++++++++++++++++++-------------- internal/website/mux.go | 16 ++++++++++---- nix/gomod2nix.toml | 7 ++++-- 5 files changed, 60 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 989a82c..7d6d42b 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/deckarep/golang-set/v2 v2.6.0 github.com/fatih/structtag v1.2.0 github.com/fsnotify/fsnotify v1.7.0 + github.com/kevinpollet/nego v0.0.0-20211010160919-a65cd48cee43 github.com/osdevisnot/sorvor v0.4.4 github.com/pkg/errors v0.9.1 github.com/snabb/sitemap v1.0.4 @@ -45,6 +46,6 @@ require ( github.com/snabb/diagio v1.0.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/text v0.16.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8fc9606..b7b12b7 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/kevinpollet/nego v0.0.0-20211010160919-a65cd48cee43 h1:Pdirg1gwhEcGjMLyuSxGn9664p+P8J9SrfMgpFwrDyg= +github.com/kevinpollet/nego v0.0.0-20211010160919-a65cd48cee43/go.mod h1:ahLMuLCUyDdXqtqGyuwGev7/PGtO7r7ocvdwDuEN/3E= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -146,8 +148,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 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 })) diff --git a/nix/gomod2nix.toml b/nix/gomod2nix.toml index 459fc49..f797587 100644 --- a/nix/gomod2nix.toml +++ b/nix/gomod2nix.toml @@ -74,6 +74,9 @@ schema = 3 [mod."github.com/gorilla/css"] version = "v1.0.1" hash = "sha256-6JwNHqlY2NpZ0pSQTyYPSpiNqjXOdFHqrUT10sv3y8A=" + [mod."github.com/kevinpollet/nego"] + version = "v0.0.0-20211010160919-a65cd48cee43" + hash = "sha256-pmlOiyybXPtEDi/QYAA8rXN3wf12/Zd2rheC9D47x0g=" [mod."github.com/microcosm-cc/bluemonday"] version = "v1.0.26" hash = "sha256-ZX4QUWHVEoGBeTHfPcLD5XoiubeO8GhkdqkC4Me8nRE=" @@ -114,8 +117,8 @@ schema = 3 version = "v0.20.0" hash = "sha256-mowlaoG2k4n1c1rApWef5EMiXd3I77CsUi8jPh6pTYA=" [mod."golang.org/x/text"] - version = "v0.15.0" - hash = "sha256-pBnj0AEkfkvZf+3bN7h6epCD2kurw59clDP7yWvxKlk=" + version = "v0.16.0" + hash = "sha256-hMTO45upjEuA4sJzGplJT+La2n3oAfHccfYWZuHcH+8=" [mod."gopkg.in/yaml.v2"] version = "v2.4.0" hash = "sha256-uVEGglIedjOIGZzHW4YwN1VoRSTK8o0eGZqzd+TNdd0=" -- cgit 1.4.1