@@ -161,6 +161,12 @@ type FileServer struct {
161
161
PrecompressedOrder []string `json:"precompressed_order,omitempty"`
162
162
precompressors map [string ]encode.Precompressed
163
163
164
+ // List of file extensions to try to read Etags from.
165
+ // If set, file Etags will be read from sidecar files
166
+ // with any of these suffixes, instead of generating
167
+ // our own Etag.
168
+ EtagFileExtensions []string `json:"etag_file_extensions,omitempty"`
169
+
164
170
fsmap caddy.FileSystems
165
171
166
172
logger * zap.Logger
@@ -396,6 +402,14 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
396
402
w .Header ().Del ("Accept-Ranges" )
397
403
w .Header ().Add ("Vary" , "Accept-Encoding" )
398
404
405
+ // try to get the etag from pre computed files if an etag suffix list was provided
406
+ if etag == "" && fsrv .EtagFileExtensions != nil {
407
+ etag , err = fsrv .getEtagFromFile (fileSystem , compressedFilename )
408
+ if err != nil {
409
+ return err
410
+ }
411
+ }
412
+
399
413
// don't assign info = compressedInfo because sidecars are kind
400
414
// of transparent; however we do need to set the Etag:
401
415
// https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793
@@ -420,7 +434,13 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
420
434
return err // error is already structured
421
435
}
422
436
defer file .Close ()
423
-
437
+ // try to get the etag from pre computed files if an etag suffix list was provided
438
+ if etag == "" && fsrv .EtagFileExtensions != nil {
439
+ etag , err = fsrv .getEtagFromFile (fileSystem , filename )
440
+ if err != nil {
441
+ return err
442
+ }
443
+ }
424
444
if etag == "" {
425
445
etag = calculateEtag (info )
426
446
}
@@ -639,6 +659,22 @@ func calculateEtag(d os.FileInfo) string {
639
659
return `"` + t + s + `"`
640
660
}
641
661
662
+ // Finds the first corresponding etag file for a given file in the file system and return its content
663
+ func (fsrv * FileServer ) getEtagFromFile (fileSystem fs.FS , filename string ) (string , error ) {
664
+ for _ , suffix := range fsrv .EtagFileExtensions {
665
+ etagFilename := filename + suffix
666
+ etag , err := fs .ReadFile (fileSystem , etagFilename )
667
+ if errors .Is (err , fs .ErrNotExist ) {
668
+ continue
669
+ }
670
+ if err != nil {
671
+ return "" , fmt .Errorf ("cannot read etag from file %s: %v" , etagFilename , err )
672
+ }
673
+ return string (etag ), nil
674
+ }
675
+ return "" , nil
676
+ }
677
+
642
678
// redirect performs a redirect to a given path. The 'toPath' parameter
643
679
// MUST be solely a path, and MUST NOT include a query.
644
680
func redirect (w http.ResponseWriter , r * http.Request , toPath string ) error {
0 commit comments