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  | 
