-
-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fileserver: Support precompressed
files without base files
#5745
Changes from all commits
d1ef85b
029a791
4e97c97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -132,6 +132,8 @@ type FileServer struct { | |||
// clobbering the explicit rewrite with implicit behavior. | ||||
CanonicalURIs *bool `json:"canonical_uris,omitempty"` | ||||
|
||||
PreferPrecompressed bool `json:"prefer_precompressed,omitempty"` | ||||
|
||||
// Override the status code written when successfully serving a file. | ||||
// Particularly useful when explicitly serving a file as display for | ||||
// an error, like a 404 page. A placeholder may be used. By default, | ||||
|
@@ -255,99 +257,104 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c | |||
zap.String("request_path", r.URL.Path), | ||||
zap.String("result", filename)) | ||||
|
||||
// get information about the file | ||||
info, err := fs.Stat(fsrv.fileSystem, filename) | ||||
if err != nil { | ||||
err = fsrv.mapDirOpenError(err, filename) | ||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) { | ||||
return fsrv.notFound(w, r, next) | ||||
} else if errors.Is(err, fs.ErrPermission) { | ||||
return caddyhttp.Error(http.StatusForbidden, err) | ||||
} | ||||
return caddyhttp.Error(http.StatusInternalServerError, err) | ||||
} | ||||
var info fs.FileInfo | ||||
var err error | ||||
|
||||
// if the request mapped to a directory, see if | ||||
// there is an index file we can serve | ||||
var implicitIndexFile bool | ||||
if info.IsDir() && len(fsrv.IndexNames) > 0 { | ||||
for _, indexPage := range fsrv.IndexNames { | ||||
indexPage := repl.ReplaceAll(indexPage, "") | ||||
indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage) | ||||
if fileHidden(indexPath, filesToHide) { | ||||
// pretend this file doesn't exist | ||||
fsrv.logger.Debug("hiding index file", | ||||
zap.String("filename", indexPath), | ||||
zap.Strings("files_to_hide", filesToHide)) | ||||
continue | ||||
if !fsrv.PreferPrecompressed { | ||||
// get information about the file | ||||
info, err = fs.Stat(fsrv.fileSystem, filename) | ||||
if err != nil { | ||||
err = fsrv.mapDirOpenError(err, filename) | ||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) { | ||||
return fsrv.notFound(w, r, next) | ||||
} else if errors.Is(err, fs.ErrPermission) { | ||||
return caddyhttp.Error(http.StatusForbidden, err) | ||||
} | ||||
return caddyhttp.Error(http.StatusInternalServerError, err) | ||||
} | ||||
|
||||
indexInfo, err := fs.Stat(fsrv.fileSystem, indexPath) | ||||
if err != nil { | ||||
continue | ||||
} | ||||
// if the request mapped to a directory, see if | ||||
// there is an index file we can serve | ||||
var implicitIndexFile bool | ||||
if info.IsDir() && len(fsrv.IndexNames) > 0 { | ||||
for _, indexPage := range fsrv.IndexNames { | ||||
indexPage := repl.ReplaceAll(indexPage, "") | ||||
indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage) | ||||
if fileHidden(indexPath, filesToHide) { | ||||
// pretend this file doesn't exist | ||||
fsrv.logger.Debug("hiding index file", | ||||
zap.String("filename", indexPath), | ||||
zap.Strings("files_to_hide", filesToHide)) | ||||
continue | ||||
} | ||||
|
||||
// don't rewrite the request path to append | ||||
// the index file, because we might need to | ||||
// do a canonical-URL redirect below based | ||||
// on the URL as-is | ||||
indexInfo, err := fs.Stat(fsrv.fileSystem, indexPath) | ||||
if err != nil { | ||||
continue | ||||
} | ||||
|
||||
// we've chosen to use this index file, | ||||
// so replace the last file info and path | ||||
// with that of the index file | ||||
info = indexInfo | ||||
filename = indexPath | ||||
implicitIndexFile = true | ||||
fsrv.logger.Debug("located index file", zap.String("filename", filename)) | ||||
break | ||||
// don't rewrite the request path to append | ||||
// the index file, because we might need to | ||||
// do a canonical-URL redirect below based | ||||
// on the URL as-is | ||||
|
||||
// we've chosen to use this index file, | ||||
// so replace the last file info and path | ||||
// with that of the index file | ||||
info = indexInfo | ||||
filename = indexPath | ||||
implicitIndexFile = true | ||||
fsrv.logger.Debug("located index file", zap.String("filename", filename)) | ||||
break | ||||
} | ||||
} | ||||
} | ||||
|
||||
// if still referencing a directory, delegate | ||||
// to browse or return an error | ||||
if info.IsDir() { | ||||
fsrv.logger.Debug("no index file in directory", | ||||
zap.String("path", filename), | ||||
zap.Strings("index_filenames", fsrv.IndexNames)) | ||||
if fsrv.Browse != nil && !fileHidden(filename, filesToHide) { | ||||
return fsrv.serveBrowse(root, filename, w, r, next) | ||||
// if still referencing a directory, delegate | ||||
// to browse or return an error | ||||
if info.IsDir() { | ||||
fsrv.logger.Debug("no index file in directory", | ||||
zap.String("path", filename), | ||||
zap.Strings("index_filenames", fsrv.IndexNames)) | ||||
if fsrv.Browse != nil && !fileHidden(filename, filesToHide) { | ||||
return fsrv.serveBrowse(root, filename, w, r, next) | ||||
} | ||||
return fsrv.notFound(w, r, next) | ||||
} | ||||
return fsrv.notFound(w, r, next) | ||||
} | ||||
|
||||
// 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) { | ||||
fsrv.logger.Debug("hiding file", | ||||
zap.String("filename", filename), | ||||
zap.Strings("files_to_hide", filesToHide)) | ||||
return fsrv.notFound(w, r, next) | ||||
} | ||||
// 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) { | ||||
fsrv.logger.Debug("hiding file", | ||||
zap.String("filename", filename), | ||||
zap.Strings("files_to_hide", filesToHide)) | ||||
return fsrv.notFound(w, r, next) | ||||
} | ||||
|
||||
// if URL canonicalization is enabled, we need to enforce trailing | ||||
// slash convention: if a directory, trailing slash; if a file, no | ||||
// trailing slash - not enforcing this can break relative hrefs | ||||
// in HTML (see https://github.com/caddyserver/caddy/issues/2741) | ||||
if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs { | ||||
// Only redirect if the last element of the path (the filename) was not | ||||
// rewritten; if the admin wanted to rewrite to the canonical path, they | ||||
// would have, and we have to be very careful not to introduce unwanted | ||||
// redirects and especially redirect loops! | ||||
// See https://github.com/caddyserver/caddy/issues/4205. | ||||
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) | ||||
if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { | ||||
if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") { | ||||
to := origReq.URL.Path + "/" | ||||
fsrv.logger.Debug("redirecting to canonical URI (adding trailing slash for directory)", | ||||
zap.String("from_path", origReq.URL.Path), | ||||
zap.String("to_path", to)) | ||||
return redirect(w, r, to) | ||||
} else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") { | ||||
to := origReq.URL.Path[:len(origReq.URL.Path)-1] | ||||
fsrv.logger.Debug("redirecting to canonical URI (removing trailing slash for file)", | ||||
zap.String("from_path", origReq.URL.Path), | ||||
zap.String("to_path", to)) | ||||
return redirect(w, r, to) | ||||
// if URL canonicalization is enabled, we need to enforce trailing | ||||
// slash convention: if a directory, trailing slash; if a file, no | ||||
// trailing slash - not enforcing this can break relative hrefs | ||||
// in HTML (see https://github.com/caddyserver/caddy/issues/2741) | ||||
if fsrv.CanonicalURIs == nil || *fsrv.CanonicalURIs { | ||||
// Only redirect if the last element of the path (the filename) was not | ||||
// rewritten; if the admin wanted to rewrite to the canonical path, they | ||||
// would have, and we have to be very careful not to introduce unwanted | ||||
// redirects and especially redirect loops! | ||||
// See https://github.com/caddyserver/caddy/issues/4205. | ||||
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request) | ||||
if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) { | ||||
if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") { | ||||
to := origReq.URL.Path + "/" | ||||
fsrv.logger.Debug("redirecting to canonical URI (adding trailing slash for directory)", | ||||
zap.String("from_path", origReq.URL.Path), | ||||
zap.String("to_path", to)) | ||||
return redirect(w, r, to) | ||||
} else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") { | ||||
to := origReq.URL.Path[:len(origReq.URL.Path)-1] | ||||
fsrv.logger.Debug("redirecting to canonical URI (removing trailing slash for file)", | ||||
zap.String("from_path", origReq.URL.Path), | ||||
zap.String("to_path", to)) | ||||
return redirect(w, r, to) | ||||
} | ||||
} | ||||
} | ||||
} | ||||
|
@@ -364,8 +371,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c | |||
continue | ||||
} | ||||
compressedFilename := filename + precompress.Suffix() | ||||
compressedInfo, err := fs.Stat(fsrv.fileSystem, compressedFilename) | ||||
if err != nil || compressedInfo.IsDir() { | ||||
info, err = fs.Stat(fsrv.fileSystem, compressedFilename) | ||||
if err != nil || info.IsDir() { | ||||
fsrv.logger.Debug("precompressed file not accessible", zap.String("filename", compressedFilename), zap.Error(err)) | ||||
continue | ||||
} | ||||
|
@@ -388,17 +395,26 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c | |||
// of transparent; however we do need to set the Etag: | ||||
// https://caddy.community/t/gzipped-sidecar-file-wrong-same-etag/16793 | ||||
if etag == "" { | ||||
etag = calculateEtag(compressedInfo) | ||||
etag = calculateEtag(info) | ||||
} | ||||
|
||||
break | ||||
} | ||||
|
||||
// no precompressed file found, use the actual file | ||||
if file == nil { | ||||
fsrv.logger.Debug("opening file", zap.String("filename", filename)) | ||||
|
||||
Comment on lines
-399
to
-400
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should preserve the debug log, it's helpful for tracing. In fact, maybe we should add more debug logs in here relating to which file is being picked depending on the precompressed options. |
||||
// open the file | ||||
info, err = fs.Stat(fsrv.fileSystem, filename) | ||||
if err != nil { | ||||
err = fsrv.mapDirOpenError(err, filename) | ||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) { | ||||
return fsrv.notFound(w, r, next) | ||||
} else if errors.Is(err, fs.ErrPermission) { | ||||
return caddyhttp.Error(http.StatusForbidden, err) | ||||
} | ||||
return caddyhttp.Error(http.StatusInternalServerError, err) | ||||
} | ||||
|
||||
file, err = fsrv.openFile(filename, w) | ||||
if err != nil { | ||||
if herr, ok := err.(caddyhttp.HandlerError); ok && | ||||
|
@@ -476,6 +492,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c | |||
// that errors generated by ServeContent are written immediately | ||||
// to the response, so we cannot handle them (but errors there | ||||
// are rare) | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need this newline anymore
Suggested change
|
||||
http.ServeContent(w, r, info.Name(), info.ModTime(), file.(io.ReadSeeker)) | ||||
|
||||
return nil | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add godoc comments to this field, otherwise it'll appear without documentation on the Caddy website (in the JSON config section).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Let me add the godoc comments to this field.