summaryrefslogtreecommitdiff
path: root/caddyconfig/httpcaddyfile
diff options
context:
space:
mode:
Diffstat (limited to 'caddyconfig/httpcaddyfile')
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go33
-rw-r--r--caddyconfig/httpcaddyfile/directives.go1
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go138
-rw-r--r--caddyconfig/httpcaddyfile/parser_test.go4
4 files changed, 137 insertions, 39 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index b523d95..ebb03cc 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -33,6 +33,7 @@ func init() {
RegisterDirective("tls", parseTLS)
RegisterHandlerDirective("redir", parseRedir)
RegisterHandlerDirective("respond", parseRespond)
+ RegisterHandlerDirective("route", parseRoute)
}
func parseBind(h Helper) ([]ConfigValue, error) {
@@ -297,3 +298,35 @@ func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
}
return sr, nil
}
+
+func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
+ sr := new(caddyhttp.Subroute)
+
+ for h.Next() {
+ for nesting := h.Nesting(); h.NextBlock(nesting); {
+ dir := h.Val()
+
+ dirFunc, ok := registeredDirectives[dir]
+ if !ok {
+ return nil, h.Errf("unrecognized directive: %s", dir)
+ }
+
+ subHelper := h
+ subHelper.Dispenser = h.NewFromNextTokens()
+
+ results, err := dirFunc(subHelper)
+ if err != nil {
+ return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
+ }
+ for _, result := range results {
+ handler, ok := result.Value.(caddyhttp.Route)
+ if !ok {
+ return nil, h.Errf("%s directive returned something other than an HTTP route: %#v (only handler directives can be used in routes)", dir, result.Value)
+ }
+ sr.Routes = append(sr.Routes, handler)
+ }
+ }
+ }
+
+ return sr, nil
+}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 8f30db2..19ecb26 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -36,6 +36,7 @@ var defaultDirectiveOrder = []string{
"request_header",
"encode",
"templates",
+ "route",
"redir",
"respond",
"reverse_proxy",
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index d8fde46..64b93b0 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -114,37 +114,45 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
}
// extract matcher definitions
- d := sb.block.DispenseDirective("matcher")
- matcherDefs, err := parseMatcherDefinitions(d)
- if err != nil {
- return nil, warnings, err
+ matcherDefs := make(map[string]caddy.ModuleMap)
+ for _, segment := range sb.block.Segments {
+ if dir := segment.Directive(); strings.HasPrefix(dir, matcherPrefix) {
+ d := sb.block.DispenseDirective(dir)
+ err := parseMatcherDefinitions(d, matcherDefs)
+ if err != nil {
+ return nil, warnings, err
+ }
+ }
}
for _, segment := range sb.block.Segments {
dir := segment.Directive()
- if dir == "matcher" {
- // TODO: This is a special case because we pre-processed it; handle this better
+
+ if strings.HasPrefix(dir, matcherPrefix) {
+ // matcher definitions were pre-processed
continue
}
- if dirFunc, ok := registeredDirectives[dir]; ok {
- results, err := dirFunc(Helper{
- Dispenser: caddyfile.NewDispenser(segment),
- options: options,
- warnings: &warnings,
- matcherDefs: matcherDefs,
- parentBlock: sb.block,
- })
- if err != nil {
- return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
- }
- for _, result := range results {
- result.directive = dir
- sb.pile[result.Class] = append(sb.pile[result.Class], result)
- }
- } else {
+
+ dirFunc, ok := registeredDirectives[dir]
+ if !ok {
tkn := segment[0]
return nil, warnings, fmt.Errorf("%s:%d: unrecognized directive: %s", tkn.File, tkn.Line, dir)
}
+
+ results, err := dirFunc(Helper{
+ Dispenser: caddyfile.NewDispenser(segment),
+ options: options,
+ warnings: &warnings,
+ matcherDefs: matcherDefs,
+ parentBlock: sb.block,
+ })
+ if err != nil {
+ return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
+ }
+ for _, result := range results {
+ result.directive = dir
+ sb.pile[result.Class] = append(sb.pile[result.Class], result)
+ }
}
}
@@ -372,10 +380,63 @@ func (st *ServerType) serversFromPairings(
}
sort.SliceStable(dirRoutes, func(i, j int) bool {
iDir, jDir := dirRoutes[i].directive, dirRoutes[j].directive
+ if iDir == jDir {
+ // TODO: we really need to refactor this into a separate function or method...
+ // sub-sort by path matcher length, if there's only one
+ iRoute := dirRoutes[i].Value.(caddyhttp.Route)
+ jRoute := dirRoutes[j].Value.(caddyhttp.Route)
+ if len(iRoute.MatcherSetsRaw) == 1 && len(jRoute.MatcherSetsRaw) == 1 {
+ // for slightly better efficiency, only decode the path matchers once,
+ // then just store them arbitrarily in the decoded MatcherSets field,
+ // ours should be the only thing in there
+ var iPM, jPM caddyhttp.MatchPath
+ if len(iRoute.MatcherSets) == 1 {
+ iPM = iRoute.MatcherSets[0][0].(caddyhttp.MatchPath)
+ }
+ if len(jRoute.MatcherSets) == 1 {
+ jPM = jRoute.MatcherSets[0][0].(caddyhttp.MatchPath)
+ }
+ // if it's our first time seeing this route's path matcher, decode it
+ if iPM == nil {
+ var pathMatcher caddyhttp.MatchPath
+ _ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &pathMatcher)
+ iRoute.MatcherSets = caddyhttp.MatcherSets{{pathMatcher}}
+ iPM = pathMatcher
+ }
+ if jPM == nil {
+ var pathMatcher caddyhttp.MatchPath
+ _ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &pathMatcher)
+ jRoute.MatcherSets = caddyhttp.MatcherSets{{pathMatcher}}
+ jPM = pathMatcher
+ }
+ // finally, if there is only one path in the
+ // matcher, sort by longer path first
+ if len(iPM) == 1 && len(jPM) == 1 {
+ return len(iPM[0]) > len(jPM[0])
+ }
+ }
+ }
return dirPositions[iDir] < dirPositions[jDir]
})
}
+
+ // add all the routes piled in from directives
for _, r := range dirRoutes {
+ // as a special case, group rewrite directives so that they are mutually exclusive;
+ // this means that only the first matching rewrite will be evaluated, and that's
+ // probably a good thing, since there should never be a need to do more than one
+ // rewrite (I think?), and cascading rewrites smell bad... imagine these rewrites:
+ // rewrite /docs/json/* /docs/json/index.html
+ // rewrite /docs/* /docs/index.html
+ // (We use this on the Caddy website, or at least we did once.) The first rewrite's
+ // result is also matched by the second rewrite, making the first rewrite pointless.
+ // See issue #2959.
+ if r.directive == "rewrite" {
+ route := r.Value.(caddyhttp.Route)
+ route.Group = "rewriting"
+ r.Value = route
+ }
+
handlerSubroute.Routes = append(handlerSubroute.Routes, r.Value.(caddyhttp.Route))
}
@@ -480,17 +541,16 @@ func matcherSetFromMatcherToken(
if tkn.Text == "*" {
// match all requests == no matchers, so nothing to do
return nil, true, nil
- } else if strings.HasPrefix(tkn.Text, "/") || strings.HasPrefix(tkn.Text, "=/") {
+ } else if strings.HasPrefix(tkn.Text, "/") {
// convenient way to specify a single path match
return caddy.ModuleMap{
"path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings),
}, true, nil
- } else if strings.HasPrefix(tkn.Text, "match:") {
+ } else if strings.HasPrefix(tkn.Text, matcherPrefix) {
// pre-defined matcher
- matcherName := strings.TrimPrefix(tkn.Text, "match:")
- m, ok := matcherDefs[matcherName]
+ m, ok := matcherDefs[tkn.Text]
if !ok {
- return nil, false, fmt.Errorf("unrecognized matcher name: %+v", matcherName)
+ return nil, false, fmt.Errorf("unrecognized matcher name: %+v", tkn.Text)
}
return m, true, nil
}
@@ -577,35 +637,37 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
return matcherSetsEnc, nil
}
-func parseMatcherDefinitions(d *caddyfile.Dispenser) (map[string]caddy.ModuleMap, error) {
- matchers := make(map[string]caddy.ModuleMap)
+func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error {
for d.Next() {
definitionName := d.Val()
+
+ if _, ok := matchers[definitionName]; ok {
+ return fmt.Errorf("matcher is defined more than once: %s", definitionName)
+ }
+ matchers[definitionName] = make(caddy.ModuleMap)
+
for nesting := d.Nesting(); d.NextBlock(nesting); {
matcherName := d.Val()
mod, err := caddy.GetModule("http.matchers." + matcherName)
if err != nil {
- return nil, fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
+ return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
}
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
- return nil, fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
+ return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextTokens())
if err != nil {
- return nil, err
+ return err
}
rm, ok := unm.(caddyhttp.RequestMatcher)
if !ok {
- return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
- }
- if _, ok := matchers[definitionName]; !ok {
- matchers[definitionName] = make(caddy.ModuleMap)
+ return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
}
matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
}
}
- return matchers, nil
+ return nil
}
func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.ModuleMap, error) {
@@ -643,5 +705,7 @@ type sbAddrAssociation struct {
serverBlocks []serverBlock
}
+const matcherPrefix = "@"
+
// Interface guard
var _ caddyfile.ServerType = (*ServerType)(nil)
diff --git a/caddyconfig/httpcaddyfile/parser_test.go b/caddyconfig/httpcaddyfile/parser_test.go
index bcecf66..ae5751c 100644
--- a/caddyconfig/httpcaddyfile/parser_test.go
+++ b/caddyconfig/httpcaddyfile/parser_test.go
@@ -29,7 +29,7 @@ func TestParse(t *testing.T) {
}{
{
input: `http://localhost
- matcher debug {
+ @debug {
query showdebug=1
}
`,
@@ -38,7 +38,7 @@ func TestParse(t *testing.T) {
},
{
input: `http://localhost
- matcher debug {
+ @debug {
query bad format
}
`,