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
{