Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 05:45 UTC
fileserver: Add `first_exist_fallback` strategy for `try_files` (#6699)
* feat: add first_exist_or_fallback strategy for try_files * fix tests * linter
Kévin Dunglas committed
Dec 3, 2024 at 13:44 UTC
efd9251ad38a4fd9f7d900445400ac3c8e564c28
7 files changed
+40
-14
caddytest/integration/caddyfile_adapt/php_fastcgi_handle_response.caddyfiletest
+1
@@ -58,6 +58,7 @@
58
"{http.request.uri.path}/index.php",
59
"index.php"
60
],
61
+ "try_policy": "first_exist_fallback",
62
"split_path": [
63
".php"
64
]
caddytest/integration/caddyfile_adapt/php_fastcgi_matcher.caddyfiletest
+2
-1
@@ -73,7 +73,8 @@ php_fastcgi @test localhost:9000
73
"{http.request.uri.path}",
74
"{http.request.uri.path}/index.php",
75
"index.php"
76
- ]
76
+ ],
77
+ "try_policy": "first_exist_fallback"
78
}
79
}
80
]
caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.caddyfiletest
+1
@@ -59,6 +59,7 @@ php_fastcgi localhost:9000 {
59
"{http.request.uri.path}/index.php5",
60
"index.php5"
61
],
62
+ "try_policy": "first_exist_fallback",
63
"split_path": [
64
".php",
65
".php5"
caddytest/integration/caddyfile_adapt/php_fastcgi_try_files_override_no_dir_index.caddyfiletest
+1
@@ -32,6 +32,7 @@ php_fastcgi localhost:9000 {
32
"{http.request.uri.path}",
33
"index.php"
34
],
35
+ "try_policy": "first_exist_fallback",
36
"split_path": [
37
".php",
38
".php5"
modules/caddyhttp/fileserver/caddyfile.go
+1
-1
@@ -274,7 +274,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
274
tryPolicy = h.Val()
275
276
switch tryPolicy {
277
- case tryPolicyFirstExist, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
277
+ case tryPolicyFirstExist, tryPolicyFirstExistFallback, tryPolicyLargestSize, tryPolicySmallestSize, tryPolicyMostRecentlyMod:
278
default:
279
return nil, h.Errf("unrecognized try policy: %s", tryPolicy)
280
}
modules/caddyhttp/fileserver/matcher.go
+26
-12
@@ -90,6 +90,7 @@ type MatchFile struct {
90
// How to choose a file in TryFiles. Can be:
91
//
92
// - first_exist
93
+ // - first_exist_fallback
94
// - smallest_size
95
// - largest_size
96
// - most_recently_modified
@@ -415,13 +416,13 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
416
}
417
418
// setPlaceholders creates the placeholders for the matched file
418
- setPlaceholders := func(candidate matchCandidate, info fs.FileInfo) {
419
+ setPlaceholders := func(candidate matchCandidate, isDir bool) {
420
repl.Set("http.matchers.file.relative", filepath.ToSlash(candidate.relative))
421
repl.Set("http.matchers.file.absolute", filepath.ToSlash(candidate.fullpath))
422
repl.Set("http.matchers.file.remainder", filepath.ToSlash(candidate.splitRemainder))
423
424
fileType := "file"
424
- if info.IsDir() {
425
+ if isDir {
426
fileType = "directory"
427
}
428
repl.Set("http.matchers.file.type", fileType)
@@ -429,8 +430,13 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
430
431
// match file according to the configured policy
432
switch m.TryPolicy {
432
- case "", tryPolicyFirstExist:
433
- for _, pattern := range m.TryFiles {
433
+ case "", tryPolicyFirstExist, tryPolicyFirstExistFallback:
434
+ maxI := -1
435
+ if m.TryPolicy == tryPolicyFirstExistFallback {
436
+ maxI = len(m.TryFiles) - 1
437
+ }
438
+
439
+ for i, pattern := range m.TryFiles {
440
// If the pattern is a status code, emit an error,
441
// which short-circuits the middleware pipeline and
442
// writes an HTTP error response.
@@ -440,8 +446,15 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
446
447
candidates := makeCandidates(pattern)
448
for _, c := range candidates {
449
+ // Skip the IO if using fallback policy and it's the latest item
450
+ if i == maxI {
451
+ setPlaceholders(c, false)
452
+
453
+ return true, nil
454
+ }
455
+
456
if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists {
444
- setPlaceholders(c, info)
457
+ setPlaceholders(c, info.IsDir())
458
return true, nil
459
}
460
}
@@ -465,7 +478,7 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
478
if largestInfo == nil {
479
return false, nil
480
}
468
- setPlaceholders(largest, largestInfo)
481
+ setPlaceholders(largest, largestInfo.IsDir())
482
return true, nil
483
484
case tryPolicySmallestSize:
@@ -486,7 +499,7 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
499
if smallestInfo == nil {
500
return false, nil
501
}
489
- setPlaceholders(smallest, smallestInfo)
502
+ setPlaceholders(smallest, smallestInfo.IsDir())
503
return true, nil
504
505
case tryPolicyMostRecentlyMod:
@@ -506,7 +519,7 @@ func (m MatchFile) selectFile(r *http.Request) (bool, error) {
519
if recentInfo == nil {
520
return false, nil
521
}
509
- setPlaceholders(recent, recentInfo)
522
+ setPlaceholders(recent, recentInfo.IsDir())
523
return true, nil
524
}
525
@@ -708,10 +721,11 @@ var globSafeRepl = strings.NewReplacer(
721
)
722
723
const (
711
- tryPolicyFirstExist = "first_exist"
712
- tryPolicyLargestSize = "largest_size"
713
- tryPolicySmallestSize = "smallest_size"
714
- tryPolicyMostRecentlyMod = "most_recently_modified"
724
+ tryPolicyFirstExist = "first_exist"
725
+ tryPolicyFirstExistFallback = "first_exist_fallback"
726
+ tryPolicyLargestSize = "largest_size"
727
+ tryPolicySmallestSize = "smallest_size"
728
+ tryPolicyMostRecentlyMod = "most_recently_modified"
729
)
730
731
// Interface guards
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+8
@@ -18,6 +18,7 @@ import (
18
"encoding/json"
19
"net/http"
20
"strconv"
21
+ "strings"
22
23
"github.com/caddyserver/caddy/v2"
24
"github.com/caddyserver/caddy/v2/caddyconfig"
@@ -312,12 +313,18 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
313
if indexFile != "off" {
314
dirRedir := false
315
dirIndex := "{http.request.uri.path}/" + indexFile
316
+ tryPolicy := "first_exist_fallback"
317
318
// if tryFiles wasn't overridden, use a reasonable default
319
if len(tryFiles) == 0 {
320
tryFiles = []string{"{http.request.uri.path}", dirIndex, indexFile}
321
dirRedir = true
322
} else {
323
+ if !strings.HasSuffix(tryFiles[len(tryFiles)-1], ".php") {
324
+ // use first_exist strategy if the last file is not a PHP file
325
+ tryPolicy = ""
326
+ }
327
+
328
for _, tf := range tryFiles {
329
if tf == dirIndex {
330
dirRedir = true
@@ -357,6 +364,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
364
rewriteMatcherSet := caddy.ModuleMap{
365
"file": h.JSON(fileserver.MatchFile{
366
TryFiles: tryFiles,
367
+ TryPolicy: tryPolicy,
368
SplitPath: extensions,
369
}),
370
}