aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAziz Rmadi <[email protected]>2024-01-16 00:24:17 -0600
committerGitHub <[email protected]>2024-01-16 01:24:17 -0500
commit4181c79a8130a59c40c76437e15265452422ccb1 (patch)
treefdbe25dec45d958720f0581439b39a9f37da28fb
parent5e2f1b5ced5e7153f9748477612cf46188470ca7 (diff)
downloadcaddy-4181c79a8130a59c40c76437e15265452422ccb1.tar.gz
caddy-4181c79a8130a59c40c76437e15265452422ccb1.zip
httpcaddyfile: Add optional status code argument to `handle_errors` directive (#5965)
Co-authored-by: Aziz Rmadi <[email protected]>
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go59
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go11
-rw-r--r--caddytest/integration/caddyfile_adapt/error_multi_site_blocks.txt245
-rw-r--r--caddytest/integration/caddyfile_adapt/error_range_codes.txt120
-rw-r--r--caddytest/integration/caddyfile_adapt/error_range_simple_codes.txt153
-rw-r--r--caddytest/integration/caddyfile_adapt/error_simple_codes.txt120
-rw-r--r--caddytest/integration/caddyfile_adapt/error_sort.txt148
-rw-r--r--caddytest/integration/caddyfile_test.go88
8 files changed, 942 insertions, 2 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 3b56e0739..bf95a3616 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -844,10 +844,67 @@ func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
}
func parseHandleErrors(h Helper) ([]ConfigValue, error) {
- subroute, err := ParseSegmentAsSubroute(h)
+ h.Next()
+ args := h.RemainingArgs()
+ expression := ""
+ if len(args) > 0 {
+ expression = ""
+ codes := []string{}
+ for _, val := range args {
+ if len(val) != 3 {
+ return nil, h.Errf("bad status value '%s'", val)
+ }
+ if strings.HasSuffix(val, "xx") {
+ val = val[:1]
+ _, err := strconv.Atoi(val)
+ if err != nil {
+ return nil, h.Errf("bad status value '%s': %v", val, err)
+ }
+ if expression != "" {
+ expression += " || "
+ }
+ expression += fmt.Sprintf("{http.error.status_code} >= %s00 && {http.error.status_code} <= %s99", val, val)
+ continue
+ }
+ _, err := strconv.Atoi(val)
+ if err != nil {
+ return nil, h.Errf("bad status value '%s': %v", val, err)
+ }
+ codes = append(codes, val)
+ }
+ if len(codes) > 0 {
+ if expression != "" {
+ expression += " || "
+ }
+ expression += "{http.error.status_code} in [" + strings.Join(codes, ", ") + "]"
+ }
+ // Reset cursor position to get ready for ParseSegmentAsSubroute
+ h.Reset()
+ h.Next()
+ h.RemainingArgs()
+ h.Prev()
+ } else {
+ // If no arguments present reset the cursor position to get ready for ParseSegmentAsSubroute
+ h.Prev()
+ }
+
+ handler, err := ParseSegmentAsSubroute(h)
if err != nil {
return nil, err
}
+ subroute, ok := handler.(*caddyhttp.Subroute)
+ if !ok {
+ return nil, h.Errf("segment was not parsed as a subroute")
+ }
+
+ if expression != "" {
+ statusMatcher := caddy.ModuleMap{
+ "expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
+ }
+ for i := range subroute.Routes {
+ subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
+ }
+ }
return []ConfigValue{
{
Class: "error_route",
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index bc2b125ef..066df3014 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -774,10 +774,19 @@ func (st *ServerType) serversFromPairings(
if srv.Errors == nil {
srv.Errors = new(caddyhttp.HTTPErrorConfig)
}
+ sort.SliceStable(errorSubrouteVals, func(i, j int) bool {
+ sri, srj := errorSubrouteVals[i].Value.(*caddyhttp.Subroute), errorSubrouteVals[j].Value.(*caddyhttp.Subroute)
+ if len(sri.Routes[0].MatcherSetsRaw) == 0 && len(srj.Routes[0].MatcherSetsRaw) != 0 {
+ return false
+ }
+ return true
+ })
+ errorsSubroute := &caddyhttp.Subroute{}
for _, val := range errorSubrouteVals {
sr := val.Value.(*caddyhttp.Subroute)
- srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, sr, matcherSetsEnc, p, warnings)
+ errorsSubroute.Routes = append(errorsSubroute.Routes, sr.Routes...)
}
+ srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, errorsSubroute, matcherSetsEnc, p, warnings)
}
// add log associations
diff --git a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.txt b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.txt
new file mode 100644
index 000000000..0e84a13c2
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.txt
@@ -0,0 +1,245 @@
+foo.localhost {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /fivehundred* "Internal Server Error" 500
+
+ handle_errors 5xx {
+ respond "Error In range [500 .. 599]"
+ }
+ handle_errors 410 {
+ respond "404 or 410 error"
+ }
+}
+
+bar.localhost {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /fivehundred* "Internal Server Error" 500
+
+ handle_errors 5xx {
+ respond "Error In range [500 .. 599] from second site"
+ }
+ handle_errors 410 {
+ respond "404 or 410 error from second site"
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "foo.localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Internal Server Error",
+ "handler": "error",
+ "status_code": 500
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/fivehundred*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ },
+ {
+ "match": [
+ {
+ "host": [
+ "bar.localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Internal Server Error",
+ "handler": "error",
+ "status_code": 500
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/fivehundred*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "foo.localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} in [410]"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599]",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 500 \u0026\u0026 {http.error.status_code} \u003c= 599"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ },
+ {
+ "match": [
+ {
+ "host": [
+ "bar.localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error from second site",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} in [410]"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599] from second site",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 500 \u0026\u0026 {http.error.status_code} \u003c= 599"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/error_range_codes.txt b/caddytest/integration/caddyfile_adapt/error_range_codes.txt
new file mode 100644
index 000000000..46b70c8e3
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_range_codes.txt
@@ -0,0 +1,120 @@
+{
+ http_port 3010
+}
+localhost:3010 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 3010,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":3010"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Not found",
+ "handler": "error",
+ "status_code": 404
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/hidden*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.txt b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.txt
new file mode 100644
index 000000000..70158830c
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.txt
@@ -0,0 +1,153 @@
+{
+ http_port 2099
+}
+localhost:2099 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /threehundred* "Moved Permanently" 301
+ error /internalerr* "Internal Server Error" 500
+
+ handle_errors 500 3xx {
+ respond "Error code is equal to 500 or in the [300..399] range"
+ }
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 2099,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":2099"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Moved Permanently",
+ "handler": "error",
+ "status_code": 301
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/threehundred*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Internal Server Error",
+ "handler": "error",
+ "status_code": 500
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/internalerr*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "body": "Error code is equal to 500 or in the [300..399] range",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 300 \u0026\u0026 {http.error.status_code} \u003c= 399 || {http.error.status_code} in [500]"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/error_simple_codes.txt b/caddytest/integration/caddyfile_adapt/error_simple_codes.txt
new file mode 100644
index 000000000..5ac5863e3
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_simple_codes.txt
@@ -0,0 +1,120 @@
+{
+ http_port 3010
+}
+localhost:3010 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+
+ handle_errors 404 410 {
+ respond "404 or 410 error"
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 3010,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":3010"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Not found",
+ "handler": "error",
+ "status_code": 404
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/hidden*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} in [404, 410]"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/error_sort.txt b/caddytest/integration/caddyfile_adapt/error_sort.txt
new file mode 100644
index 000000000..63701cccb
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_sort.txt
@@ -0,0 +1,148 @@
+{
+ http_port 2099
+}
+localhost:2099 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+ error /internalerr* "Internal Server Error" 500
+
+ handle_errors {
+ respond "Fallback route: code outside the [400..499] range"
+ }
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 2099,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":2099"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/srv"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Internal Server Error",
+ "handler": "error",
+ "status_code": 500
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/internalerr*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Unauthorized",
+ "handler": "error",
+ "status_code": 410
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/private*"
+ ]
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "error": "Not found",
+ "handler": "error",
+ "status_code": 404
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/hidden*"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} \u003e= 400 \u0026\u0026 {http.error.status_code} \u003c= 499"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "body": "Fallback route: code outside the [400..499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_test.go b/caddytest/integration/caddyfile_test.go
index 0db7e7947..f9a98fb38 100644
--- a/caddytest/integration/caddyfile_test.go
+++ b/caddytest/integration/caddyfile_test.go
@@ -496,3 +496,91 @@ func TestUriReplace(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
}
+func TestHandleErrorSimpleCodes(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+
+ handle_errors 404 410 {
+ respond "404 or 410 error"
+ }
+ }`, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/private", 410, "404 or 410 error")
+ tester.AssertGetResponse("http://localhost:9080/hidden", 404, "404 or 410 error")
+}
+
+func TestHandleErrorRange(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+ }`, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
+ tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range")
+}
+
+func TestHandleErrorSort(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /hidden* "Not found" 404
+ error /internalerr* "Internal Server Error" 500
+
+ handle_errors {
+ respond "Fallback route: code outside the [400..499] range"
+ }
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+ }`, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Fallback route: code outside the [400..499] range")
+ tester.AssertGetResponse("http://localhost:9080/hidden", 404, "Error in the [400 .. 499] range")
+}
+
+func TestHandleErrorRangeAndCodes(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ error /private* "Unauthorized" 410
+ error /threehundred* "Moved Permanently" 301
+ error /internalerr* "Internal Server Error" 500
+
+ handle_errors 500 3xx {
+ respond "Error code is equal to 500 or in the [300..399] range"
+ }
+ handle_errors 4xx {
+ respond "Error in the [400 .. 499] range"
+ }
+ }`, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/internalerr", 500, "Error code is equal to 500 or in the [300..399] range")
+ tester.AssertGetResponse("http://localhost:9080/threehundred", 301, "Error code is equal to 500 or in the [300..399] range")
+ tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
+}