summaryrefslogtreecommitdiff
path: root/modules/caddyhttp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/caddyhttp.go38
-rw-r--r--modules/caddyhttp/matchers.go72
-rw-r--r--modules/caddyhttp/matchers_test.go20
-rw-r--r--modules/caddyhttp/routes.go64
4 files changed, 133 insertions, 61 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 2b194cd..1ff2cbc 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -53,13 +53,17 @@ func (app *App) Provision(ctx caddy2.Context) error {
for i := range srv.Listen {
srv.Listen[i] = repl.ReplaceAll(srv.Listen[i], "")
}
- err := srv.Routes.Provision(ctx)
- if err != nil {
- return fmt.Errorf("setting up server routes: %v", err)
+ if srv.Routes != nil {
+ err := srv.Routes.Provision(ctx)
+ if err != nil {
+ return fmt.Errorf("setting up server routes: %v", err)
+ }
}
- err = srv.Errors.Routes.Provision(ctx)
- if err != nil {
- return fmt.Errorf("setting up server error handling routes: %v", err)
+ if srv.Errors != nil {
+ err := srv.Errors.Routes.Provision(ctx)
+ if err != nil {
+ return fmt.Errorf("setting up server error handling routes: %v", err)
+ }
}
}
@@ -187,13 +191,15 @@ func (app *App) automaticHTTPS() error {
// find all qualifying domain names, de-duplicated
domainSet := make(map[string]struct{})
for _, route := range srv.Routes {
- for _, m := range route.matchers {
- if hm, ok := m.(*MatchHost); ok {
- for _, d := range *hm {
- if !certmagic.HostQualifies(d) {
- continue
+ for _, matcherSet := range route.matcherSets {
+ for _, m := range matcherSet {
+ if hm, ok := m.(*MatchHost); ok {
+ for _, d := range *hm {
+ if !certmagic.HostQualifies(d) {
+ continue
+ }
+ domainSet[d] = struct{}{}
}
- domainSet[d] = struct{}{}
}
}
}
@@ -245,9 +251,11 @@ func (app *App) automaticHTTPS() error {
redirTo += "{http.request.uri}"
redirRoutes = append(redirRoutes, ServerRoute{
- matchers: []RequestMatcher{
- MatchProtocol("http"),
- MatchHost(domains),
+ matcherSets: []MatcherSet{
+ {
+ MatchProtocol("http"),
+ MatchHost(domains),
+ },
},
responder: Static{
StatusCode: http.StatusTemporaryRedirect, // TODO: use permanent redirect instead
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index eb57156..33300da 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -17,16 +17,35 @@ import (
)
type (
- MatchHost []string
- MatchPath []string
- MatchPathRE struct{ matchRegexp }
- MatchMethod []string
- MatchQuery url.Values
- MatchHeader http.Header
- MatchHeaderRE map[string]*matchRegexp
- MatchProtocol string
+ // MatchHost matches requests by the Host value.
+ MatchHost []string
+
+ // MatchPath matches requests by the URI's path.
+ MatchPath []string
+
+ // MatchPathRE matches requests by a regular expression on the URI's path.
+ MatchPathRE struct{ MatchRegexp }
+
+ // MatchMethod matches requests by the method.
+ MatchMethod []string
+
+ // MatchQuery matches requests by URI's query string.
+ MatchQuery url.Values
+
+ // MatchHeader matches requests by header fields.
+ MatchHeader http.Header
+
+ // MatchHeaderRE matches requests by a regular expression on header fields.
+ MatchHeaderRE map[string]*MatchRegexp
+
+ // MatchProtocol matches requests by protocol.
+ MatchProtocol string
+
+ // MatchStarlarkExpr matches requests by evaluating a Starlark expression.
MatchStarlarkExpr string
- MatchTable string // TODO: finish implementing
+
+ // MatchTable matches requests by values in the table.
+ MatchTable string // TODO: finish implementing
)
func init() {
@@ -68,6 +87,7 @@ func init() {
})
}
+// Match returns true if r matches m.
func (m MatchHost) Match(r *http.Request) bool {
outer:
for _, host := range m {
@@ -93,6 +113,7 @@ outer:
return false
}
+// Match returns true if r matches m.
func (m MatchPath) Match(r *http.Request) bool {
for _, matchPath := range m {
compare := r.URL.Path
@@ -111,11 +132,13 @@ func (m MatchPath) Match(r *http.Request) bool {
return false
}
+// Match returns true if r matches m.
func (m MatchPathRE) Match(r *http.Request) bool {
repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
- return m.match(r.URL.Path, repl, "path_regexp")
+ return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
}
+// Match returns true if r matches m.
func (m MatchMethod) Match(r *http.Request) bool {
for _, method := range m {
if r.Method == method {
@@ -125,6 +148,7 @@ func (m MatchMethod) Match(r *http.Request) bool {
return false
}
+// Match returns true if r matches m.
func (m MatchQuery) Match(r *http.Request) bool {
for param, vals := range m {
paramVal := r.URL.Query().Get(param)
@@ -137,6 +161,7 @@ func (m MatchQuery) Match(r *http.Request) bool {
return false
}
+// Match returns true if r matches m.
func (m MatchHeader) Match(r *http.Request) bool {
for field, allowedFieldVals := range m {
var match bool
@@ -157,10 +182,11 @@ func (m MatchHeader) Match(r *http.Request) bool {
return true
}
+// Match returns true if r matches m.
func (m MatchHeaderRE) Match(r *http.Request) bool {
for field, rm := range m {
repl := r.Context().Value(caddy2.ReplacerCtxKey).(caddy2.Replacer)
- match := rm.match(r.Header.Get(field), repl, "header_regexp")
+ match := rm.Match(r.Header.Get(field), repl, "header_regexp")
if !match {
return false
}
@@ -168,6 +194,7 @@ func (m MatchHeaderRE) Match(r *http.Request) bool {
return true
}
+// Provision compiles m's regular expressions.
func (m MatchHeaderRE) Provision() error {
for _, rm := range m {
err := rm.Provision()
@@ -178,6 +205,7 @@ func (m MatchHeaderRE) Provision() error {
return nil
}
+// Validate validates m's regular expressions.
func (m MatchHeaderRE) Validate() error {
for _, rm := range m {
err := rm.Validate()
@@ -188,6 +216,7 @@ func (m MatchHeaderRE) Validate() error {
return nil
}
+// Match returns true if r matches m.
func (m MatchProtocol) Match(r *http.Request) bool {
switch string(m) {
case "grpc":
@@ -200,6 +229,7 @@ func (m MatchProtocol) Match(r *http.Request) bool {
return false
}
+// Match returns true if r matches m.
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
input := string(m)
thread := new(starlark.Thread)
@@ -213,15 +243,16 @@ func (m MatchStarlarkExpr) Match(r *http.Request) bool {
return val.String() == "True"
}
-// matchRegexp is just the fields common among
-// matchers that can use regular expressions.
-type matchRegexp struct {
+// MatchRegexp is an embeddable type for matching
+// using regular expressions.
+type MatchRegexp struct {
Name string `json:"name"`
Pattern string `json:"pattern"`
compiled *regexp.Regexp
}
-func (mre *matchRegexp) Provision() error {
+// Provision compiles the regular expression.
+func (mre *MatchRegexp) Provision() error {
re, err := regexp.Compile(mre.Pattern)
if err != nil {
return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err)
@@ -230,14 +261,21 @@ func (mre *matchRegexp) Provision() error {
return nil
}
-func (mre *matchRegexp) Validate() error {
+// Validate ensures mre is set up correctly.
+func (mre *MatchRegexp) Validate() error {
if mre.Name != "" && !wordRE.MatchString(mre.Name) {
return fmt.Errorf("invalid regexp name (must contain only word characters): %s", mre.Name)
}
return nil
}
-func (mre *matchRegexp) match(input string, repl caddy2.Replacer, scope string) bool {
+// Match returns true if input matches the compiled regular
+// expression in mre. It sets values on the replacer repl
+// associated with capture groups, using the given scope
+// (namespace). Capture groups stored to repl will take on
+// the name "http.matchers.<scope>.<mre.Name>.<N>" where
+// <N> is the name or number of the capture group.
+func (mre *MatchRegexp) Match(input string, repl caddy2.Replacer, scope string) bool {
matches := mre.compiled.FindStringSubmatch(input)
if matches == nil {
return false
diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
index 30b45f6..5e62a90 100644
--- a/modules/caddyhttp/matchers_test.go
+++ b/modules/caddyhttp/matchers_test.go
@@ -176,38 +176,38 @@ func TestPathREMatcher(t *testing.T) {
expect: true,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "/"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "/"}},
input: "/",
expect: true,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "/foo"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
input: "/foo",
expect: true,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "/foo"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "/foo"}},
input: "/foo/",
expect: true,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "/bar"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "/bar"}},
input: "/foo/",
expect: false,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "^/bar"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "^/bar"}},
input: "/foo/bar",
expect: false,
},
{
- match: MatchPathRE{matchRegexp{Pattern: "^/foo/(.*)/baz$", Name: "name"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "^/foo/(.*)/baz$", Name: "name"}},
input: "/foo/bar/baz",
expect: true,
expectRepl: map[string]string{"name.1": "bar"},
},
{
- match: MatchPathRE{matchRegexp{Pattern: "^/foo/(?P<myparam>.*)/baz$", Name: "name"}},
+ match: MatchPathRE{MatchRegexp{Pattern: "^/foo/(?P<myparam>.*)/baz$", Name: "name"}},
input: "/foo/bar/baz",
expect: true,
expectRepl: map[string]string{"name.myparam": "bar"},
@@ -315,17 +315,17 @@ func TestHeaderREMatcher(t *testing.T) {
expectRepl map[string]string
}{
{
- match: MatchHeaderRE{"Field": &matchRegexp{Pattern: "foo"}},
+ match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "foo"}},
input: http.Header{"Field": []string{"foo"}},
expect: true,
},
{
- match: MatchHeaderRE{"Field": &matchRegexp{Pattern: "$foo^"}},
+ match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "$foo^"}},
input: http.Header{"Field": []string{"foobar"}},
expect: false,
},
{
- match: MatchHeaderRE{"Field": &matchRegexp{Pattern: "^foo(.*)$", Name: "name"}},
+ match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}},
input: http.Header{"Field": []string{"foobar"}},
expect: true,
expectRepl: map[string]string{"name.1": "bar"},
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go
index 07e0566..59f287e 100644
--- a/modules/caddyhttp/routes.go
+++ b/modules/caddyhttp/routes.go
@@ -12,17 +12,42 @@ import (
// middlewares, and a responder for handling HTTP
// requests.
type ServerRoute struct {
- Group string `json:"group,omitempty"`
- Matchers map[string]json.RawMessage `json:"match,omitempty"`
- Apply []json.RawMessage `json:"apply,omitempty"`
- Respond json.RawMessage `json:"respond,omitempty"`
+ Group string `json:"group,omitempty"`
+ MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
+ Apply []json.RawMessage `json:"apply,omitempty"`
+ Respond json.RawMessage `json:"respond,omitempty"`
Terminal bool `json:"terminal,omitempty"`
// decoded values
- matchers []RequestMatcher
- middleware []MiddlewareHandler
- responder Handler
+ matcherSets []MatcherSet
+ middleware []MiddlewareHandler
+ responder Handler
+}
+
+func (sr ServerRoute) anyMatcherSetMatches(r *http.Request) bool {
+ for _, ms := range sr.matcherSets {
+ if ms.Match(r) {
+ return true
+ }
+ }
+ return false
+}
+
+// MatcherSet is a set of matchers which
+// must all match in order for the request
+// to be matched successfully.
+type MatcherSet []RequestMatcher
+
+// Match returns true if the request matches all
+// matchers in mset.
+func (mset MatcherSet) Match(r *http.Request) bool {
+ for _, m := range mset {
+ if !m.Match(r) {
+ return false
+ }
+ }
+ return true
}
// RouteList is a list of server routes that can
@@ -33,14 +58,18 @@ type RouteList []ServerRoute
func (routes RouteList) Provision(ctx caddy2.Context) error {
for i, route := range routes {
// matchers
- for modName, rawMsg := range route.Matchers {
- val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
- if err != nil {
- return fmt.Errorf("loading matcher module '%s': %v", modName, err)
+ for _, matcherSet := range route.MatcherSets {
+ var matchers MatcherSet
+ for modName, rawMsg := range matcherSet {
+ val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
+ if err != nil {
+ return fmt.Errorf("loading matcher module '%s': %v", modName, err)
+ }
+ matchers = append(matchers, val.(RequestMatcher))
}
- routes[i].matchers = append(routes[i].matchers, val.(RequestMatcher))
+ routes[i].matcherSets = append(routes[i].matcherSets, matchers)
}
- routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
+ routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
// middleware
for j, rawMsg := range route.Apply {
@@ -78,13 +107,10 @@ func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Re
var responder Handler
groups := make(map[string]struct{})
-routeLoop:
for _, route := range routes {
- // see if route matches
- for _, m := range route.matchers {
- if !m.Match(req) {
- continue routeLoop
- }
+ // route must match at least one of the matcher sets
+ if !route.anyMatcherSetMatches(req) {
+ continue
}
// if route is part of a group, ensure only