mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-16 15:56:28 +03:00
prefer_precompressed
This commit is contained in:
parent
87c7127c28
commit
188dbc4e8b
1 changed files with 276 additions and 131 deletions
|
@ -15,6 +15,8 @@
|
|||
package fileserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -36,6 +38,29 @@ import (
|
|||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
)
|
||||
|
||||
type VirtualFile struct {
|
||||
content *bytes.Reader
|
||||
fileInfo fs.FileInfo
|
||||
}
|
||||
|
||||
func (vf VirtualFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vf VirtualFile) Read(p []byte) (n int, err error) {
|
||||
n, err = vf.content.Read(p)
|
||||
return
|
||||
}
|
||||
|
||||
func (vf VirtualFile) Stat() (fs.FileInfo, error) {
|
||||
return vf.fileInfo, nil
|
||||
}
|
||||
|
||||
func (vf VirtualFile) Seek(offset int64, whence int) (n int64, err error) {
|
||||
n, err = vf.content.Seek(offset, whence)
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(FileServer{})
|
||||
}
|
||||
|
@ -273,8 +298,138 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||
zap.String("request_path", r.URL.Path),
|
||||
zap.String("result", filename))
|
||||
|
||||
var file fs.File
|
||||
var info fs.FileInfo
|
||||
var err error
|
||||
respHeader := w.Header()
|
||||
|
||||
// etag is usually unset, but if the user knows what they're doing, let them override it
|
||||
etag := respHeader.Get("Etag")
|
||||
|
||||
// static file responses are often compressed, either on-the-fly
|
||||
// or with precompressed sidecar files; in any case, the headers
|
||||
// should contain "Vary: Accept-Encoding" even when not compressed
|
||||
// so caches can craft a reliable key (according to REDbot results)
|
||||
// see #5849
|
||||
respHeader.Add("Vary", "Accept-Encoding")
|
||||
|
||||
preferPrecompressed := true // TODO: move to config
|
||||
|
||||
if preferPrecompressed {
|
||||
fsrv.logger.Debug("prefer_precompressed: seeking precompressed file according to accepted encodings")
|
||||
|
||||
for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) {
|
||||
precompress, ok := fsrv.precompressors[ae]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
compressedFilename := filename + precompress.Suffix()
|
||||
compressedInfo, err := fs.Stat(fileSystem, 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 file", zap.String("filename", compressedFilename), zap.Error(err))
|
||||
file, err = fsrv.openFile(fileSystem, 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
|
||||
}
|
||||
file = nil
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
respHeader.Set("Content-Encoding", ae)
|
||||
respHeader.Del("Accept-Ranges")
|
||||
|
||||
// try to get the etag from pre computed files if an etag suffix list was provided
|
||||
if etag == "" && fsrv.EtagFileExtensions != nil {
|
||||
etag, err = fsrv.getEtagFromFile(fileSystem, compressedFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
info = compressedInfo
|
||||
|
||||
// Set the Etag:
|
||||
// https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793
|
||||
if etag == "" {
|
||||
etag = calculateEtag(compressedInfo)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if preferPrecompressed && file == nil {
|
||||
fsrv.logger.Debug("prefer_precompressed: seeking precompressed file to decompress")
|
||||
|
||||
for _, e := range fsrv.PrecompressedOrder {
|
||||
precompress, ok := fsrv.precompressors[e]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
compressedFilename := filename + precompress.Suffix()
|
||||
compressedInfo, err := fs.Stat(fileSystem, 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 file", zap.String("filename", compressedFilename), zap.Error(err))
|
||||
file, err = fsrv.openFile(fileSystem, 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
|
||||
}
|
||||
file = nil
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// TODO: handle all compressed formats, not only gzip
|
||||
r, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
fsrv.logger.Warn("instantiating new gzip reader for file failed", zap.String("filename", compressedFilename), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
var decomp bytes.Buffer
|
||||
_, err = decomp.ReadFrom(r)
|
||||
if err != nil {
|
||||
fsrv.logger.Warn("reading from gzip reader failed", zap.String("filename", compressedFilename), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
file = VirtualFile{bytes.NewReader(decomp.Bytes()), compressedInfo}
|
||||
|
||||
respHeader.Del("Accept-Ranges")
|
||||
|
||||
// try to get the etag from pre computed files if an etag suffix list was provided
|
||||
if etag == "" && fsrv.EtagFileExtensions != nil {
|
||||
etag, err = fsrv.getEtagFromFile(fileSystem, compressedFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
info = compressedInfo
|
||||
|
||||
// Set the Etag:
|
||||
// https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793
|
||||
if etag == "" {
|
||||
etag = calculateEtag(compressedInfo)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if file == nil {
|
||||
// get information about the file
|
||||
info, err := fs.Stat(fileSystem, filename)
|
||||
info, err = fs.Stat(fileSystem, filename)
|
||||
if err != nil {
|
||||
err = fsrv.mapDirOpenError(fileSystem, err, filename)
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) {
|
||||
|
@ -369,20 +524,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var file fs.File
|
||||
respHeader := w.Header()
|
||||
|
||||
// etag is usually unset, but if the user knows what they're doing, let them override it
|
||||
etag := respHeader.Get("Etag")
|
||||
|
||||
// static file responses are often compressed, either on-the-fly
|
||||
// or with precompressed sidecar files; in any case, the headers
|
||||
// should contain "Vary: Accept-Encoding" even when not compressed
|
||||
// so caches can craft a reliable key (according to REDbot results)
|
||||
// see #5849
|
||||
respHeader.Add("Vary", "Accept-Encoding")
|
||||
|
||||
if file == nil {
|
||||
// check for precompressed files
|
||||
for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) {
|
||||
precompress, ok := fsrv.precompressors[ae]
|
||||
|
@ -426,6 +570,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// no precompressed file found, use the actual file
|
||||
if file == nil {
|
||||
|
|
Loading…
Reference in a new issue