@marcel91200 / caddy Mirror · read-only / commits / 7d5108d1
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 14:15 UTC

httpcaddyfile: Add shortcut for expression matchers (#4976)

Francis Lavoie committed Sep 1, 2022 at 23:12 UTC 7d5108d132d3bccfd8d83bc3fc2dbc46afde20ae
3 files changed +65 -18
caddyconfig/caddyfile/lexer.go
+4
@@ -191,3 +191,7 @@ func Tokenize(input []byte, filename string) ([]Token, error) {
191 }
192 return tokens, nil
193 }
194 +
195 +func (t Token) Quoted() bool {
196 + return t.wasQuoted > 0
197 +}
caddyconfig/httpcaddyfile/httptype.go
+37 -10
@@ -1201,6 +1201,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
1201
1202 func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error {
1203 for d.Next() {
1204 + // this is the "name" for "named matchers"
1205 definitionName := d.Val()
1206
1207 if _, ok := matchers[definitionName]; ok {
@@ -1208,16 +1209,9 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
1209 }
1210 matchers[definitionName] = make(caddy.ModuleMap)
1211
1211 - // in case there are multiple instances of the same matcher, concatenate
1212 - // their tokens (we expect that UnmarshalCaddyfile should be able to
1213 - // handle more than one segment); otherwise, we'd overwrite other
1214 - // instances of the matcher in this set
1215 - tokensByMatcherName := make(map[string][]caddyfile.Token)
1216 - for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
1217 - matcherName := d.Val()
1218 - tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
1219 - }
1220 - for matcherName, tokens := range tokensByMatcherName {
1212 + // given a matcher name and the tokens following it, parse
1213 + // the tokens as a matcher module and record it
1214 + makeMatcher := func(matcherName string, tokens []caddyfile.Token) error {
1215 mod, err := caddy.GetModule("http.matchers." + matcherName)
1216 if err != nil {
1217 return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
@@ -1235,6 +1229,39 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
1229 return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
1230 }
1231 matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
1232 + return nil
1233 + }
1234 +
1235 + // if the next token is quoted, we can assume it's not a matcher name
1236 + // and that it's probably an 'expression' matcher
1237 + if d.NextArg() {
1238 + if d.Token().Quoted() {
1239 + err := makeMatcher("expression", []caddyfile.Token{d.Token()})
1240 + if err != nil {
1241 + return err
1242 + }
1243 + continue
1244 + }
1245 +
1246 + // if it wasn't quoted, then we need to rewind after calling
1247 + // d.NextArg() so the below properly grabs the matcher name
1248 + d.Prev()
1249 + }
1250 +
1251 + // in case there are multiple instances of the same matcher, concatenate
1252 + // their tokens (we expect that UnmarshalCaddyfile should be able to
1253 + // handle more than one segment); otherwise, we'd overwrite other
1254 + // instances of the matcher in this set
1255 + tokensByMatcherName := make(map[string][]caddyfile.Token)
1256 + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
1257 + matcherName := d.Val()
1258 + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
1259 + }
1260 + for matcherName, tokens := range tokensByMatcherName {
1261 + err := makeMatcher(matcherName, tokens)
1262 + if err != nil {
1263 + return err
1264 + }
1265 }
1266 }
1267 return nil
caddytest/integration/caddyfile_adapt/matcher_syntax.txt
+24 -8
@@ -19,27 +19,30 @@
19 @matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$`
20 respond @matcher6 "from vars_regexp matcher without name"
21
22 - @matcher7 {
22 + @matcher7 `path('/foo*') && method('GET')`
23 + respond @matcher7 "inline expression matcher shortcut"
24 +
25 + @matcher8 {
26 header Foo bar
27 header Foo foobar
28 header Bar foo
29 }
27 - respond @matcher7 "header matcher merging values of the same field"
30 + respond @matcher8 "header matcher merging values of the same field"
31
29 - @matcher8 {
32 + @matcher9 {
33 query foo=bar foo=baz bar=foo
34 query bar=baz
35 }
33 - respond @matcher8 "query matcher merging pairs with the same keys"
36 + respond @matcher9 "query matcher merging pairs with the same keys"
37
35 - @matcher9 {
38 + @matcher10 {
39 header !Foo
40 header Bar foo
41 }
39 - respond @matcher9 "header matcher with null field matcher"
42 + respond @matcher10 "header matcher with null field matcher"
43
41 - @matcher10 remote_ip private_ranges
42 - respond @matcher10 "remote_ip matcher with private ranges"
44 + @matcher11 remote_ip private_ranges
45 + respond @matcher11 "remote_ip matcher with private ranges"
46 }
47 ----------
48 {
@@ -152,6 +155,19 @@
155 }
156 ]
157 },
158 + {
159 + "match": [
160 + {
161 + "expression": "path('/foo*') \u0026\u0026 method('GET')"
162 + }
163 + ],
164 + "handle": [
165 + {
166 + "body": "inline expression matcher shortcut",
167 + "handler": "static_response"
168 + }
169 + ]
170 + },
171 {
172 "match": [
173 {