diff options
Diffstat (limited to 'modules/caddyhttp/fileserver')
-rw-r--r-- | modules/caddyhttp/fileserver/caddyfile.go | 31 | ||||
-rw-r--r-- | modules/caddyhttp/fileserver/staticfiles.go | 90 |
2 files changed, 106 insertions, 15 deletions
diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 3acbfa9..2ba53f2 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -19,8 +19,10 @@ import ( "strings" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" ) @@ -33,10 +35,11 @@ func init() { // server and configures it with this syntax: // // file_server [<matcher>] [browse] { -// root <path> -// hide <files...> -// index <files...> -// browse [<template_file>] +// root <path> +// hide <files...> +// index <files...> +// browse [<template_file>] +// precompressed <formats...> // } // func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { @@ -77,6 +80,26 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) } fsrv.Browse = new(Browse) h.Args(&fsrv.Browse.TemplateFile) + case "precompressed": + var order []string + for h.NextArg() { + modID := "http.precompressed." + h.Val() + mod, err := caddy.GetModule(modID) + if err != nil { + return nil, h.Errf("getting module named '%s': %v", modID, err) + } + inst := mod.New() + precompress, ok := inst.(encode.Precompressed) + if !ok { + return nil, h.Errf("module %s is not a precompressor; is %T", modID, inst) + } + if fsrv.PrecompressedRaw == nil { + fsrv.PrecompressedRaw = make(caddy.ModuleMap) + } + fsrv.PrecompressedRaw[h.Val()] = caddyconfig.JSON(precompress, nil) + order = append(order, h.Val()) + } + fsrv.PrecompressedOrder = order default: return nil, h.Errf("unknown subdirective '%s'", h.Val()) } diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index f58dfe0..c670788 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -31,6 +31,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" "go.uber.org/zap" ) @@ -79,6 +80,16 @@ type FileServer struct { // a 404 error. By default, this is false (disabled). PassThru bool `json:"pass_thru,omitempty"` + // Selection of encoders to use to check for precompressed files. + PrecompressedRaw caddy.ModuleMap `json:"precompressed,omitempty" caddy:"namespace=http.precompressed"` + + // If the client has no strong preference (q-factor), choose these encodings in order. + // If no order specified here, the first encoding from the Accept-Encoding header + // that both client and server support is used + PrecompressedOrder []string `json:"precompressed_order,omitempty"` + + precompressors map[string]encode.Precompressed + logger *zap.Logger } @@ -129,6 +140,32 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error { } } + mods, err := ctx.LoadModule(fsrv, "PrecompressedRaw") + if err != nil { + return fmt.Errorf("loading encoder modules: %v", err) + } + for modName, modIface := range mods.(map[string]interface{}) { + p, ok := modIface.(encode.Precompressed) + if !ok { + return fmt.Errorf("module %s is not precompressor", modName) + } + ae := p.AcceptEncoding() + if ae == "" { + return fmt.Errorf("precompressor does not specify an Accept-Encoding value") + } + suffix := p.Suffix() + if suffix == "" { + return fmt.Errorf("precompressor does not specify a Suffix value") + } + if _, ok := fsrv.precompressors[ae]; ok { + return fmt.Errorf("precompressor already added: %s", ae) + } + if fsrv.precompressors == nil { + fsrv.precompressors = make(map[string]encode.Precompressed) + } + fsrv.precompressors[ae] = p + } + return nil } @@ -205,8 +242,6 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c return fsrv.notFound(w, r, next) } - // TODO: content negotiation (brotli sidecar files, etc...) - // one last check to ensure the file isn't hidden (we might // have changed the filename from when we last checked) if fileHidden(filename, filesToHide) { @@ -230,18 +265,51 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c } } - fsrv.logger.Debug("opening file", zap.String("filename", filename)) + var file *os.File - // open the file - file, err := fsrv.openFile(filename, w) - if err != nil { - if herr, ok := err.(caddyhttp.HandlerError); ok && - herr.StatusCode == http.StatusNotFound { - return fsrv.notFound(w, r, next) + // check for precompressed files + for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) { + precompress, ok := fsrv.precompressors[ae] + if !ok { + continue + } + compressedFilename := filename + precompress.Suffix() + compressedInfo, err := os.Stat(compressedFilename) + if err != nil || compressedInfo.IsDir() { + fsrv.logger.Debug("precompressed file not accessible", zap.String("filename", compressedFilename), zap.Error(err)) + continue + } + fsrv.logger.Debug("opening compressed sidecar file", zap.String("filename", compressedFilename), zap.Error(err)) + file, err = fsrv.openFile(compressedFilename, w) + if err != nil { + fsrv.logger.Warn("opening precompressed file failed", zap.String("filename", compressedFilename), zap.Error(err)) + if caddyErr, ok := err.(caddyhttp.HandlerError); ok && caddyErr.StatusCode == http.StatusServiceUnavailable { + return err + } + continue + } + defer file.Close() + w.Header().Set("Content-Encoding", ae) + w.Header().Del("Accept-Ranges") + w.Header().Add("Vary", "Accept-Encoding") + break + } + + // no precompressed file found, use the actual file + if file == nil { + fsrv.logger.Debug("opening file", zap.String("filename", filename)) + + // open the file + file, err = fsrv.openFile(filename, w) + if err != nil { + if herr, ok := err.(caddyhttp.HandlerError); ok && + herr.StatusCode == http.StatusNotFound { + return fsrv.notFound(w, r, next) + } + return err // error is already structured } - return err // error is already structured + defer file.Close() } - defer file.Close() // set the ETag - note that a conditional If-None-Match request is handled // by http.ServeContent below, which checks against this ETag value |