Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 04:45 UTC
caddyfile: Expand imports inside named routes (#7986)
XiaoleC05 committed
Sep 16, 2026 at 21:09 UTC
c1645c544d4bd2e90524b83f41e9ea0a185051df
5 files changed
+323
-2
caddyconfig/caddyfile/parse.go
+44
-2
@@ -173,8 +173,15 @@ func (p *parser) begin() error {
173
if err != nil {
174
return err
175
}
176
- tokens = append([]Token{nameToken}, tokens...)
177
- p.block.Segments = []Segment{tokens}
176
+
177
+ // expand any import directives inside the named route block
178
+ expandedTokens, err := p.expandImportsInBlock(tokens)
179
+ if err != nil {
180
+ return err
181
+ }
182
+
183
+ expandedTokens = append([]Token{nameToken}, expandedTokens...)
184
+ p.block.Segments = []Segment{expandedTokens}
185
return nil
186
}
187
@@ -583,6 +590,41 @@ func (p *parser) doImport(nesting int) error {
590
return nil
591
}
592
593
+// expandImportsInBlock takes a slice of tokens (typically the contents of a
594
+// named route block including its outer curly braces) and expands any import
595
+// directives found at the beginning of a line. The expansion is done by
596
+// creating a temporary parser that shares the same snippets and import graph,
597
+// then looping through the tokens and calling doImport whenever an import
598
+// directive is encountered. All other tokens are left untouched.
599
+func (p *parser) expandImportsInBlock(tokens []Token) ([]Token, error) {
600
+ // Create a temporary parser that operates on the provided tokens.
601
+ // The Dispenser is initialized with the token slice; snippets and the
602
+ // import graph are shared so that imports and cycle detection work
603
+ // consistently across the whole Caddyfile.
604
+ tempParser := &parser{
605
+ Dispenser: NewDispenser(tokens),
606
+ definedSnippets: p.definedSnippets,
607
+ importGraph: p.importGraph,
608
+ }
609
+
610
+ // Loop through the tokens. We only care about import directives that
611
+ // appear at the start of a line (same logic as directives()).
612
+ for tempParser.Next() {
613
+ if tempParser.Val() == "import" && tempParser.isNewLine() {
614
+ if err := tempParser.doImport(1); err != nil {
615
+ return nil, err
616
+ }
617
+ // Roll back the cursor so the next iteration sees the first
618
+ // token of the imported content (or the next token after it).
619
+ tempParser.cursor--
620
+ }
621
+ }
622
+
623
+ // The temporary parser's token slice has been modified in place by
624
+ // doImport, so we return it directly.
625
+ return tempParser.tokens, nil
626
+}
627
+
628
// doSingleImport lexes the individual file at importFile and returns
629
// its tokens or an error, if any.
630
func (p *parser) doSingleImport(importFile string) ([]Token, error) {
caddytest/integration/caddyfile_adapt/import_inside_named_route.caddyfiletest
new
+62
@@ -0,0 +1,62 @@
1
+(common) {
2
+ respond "from snippet"
3
+}
4
+
5
+&(foo) {
6
+ import common
7
+}
8
+
9
+localhost:9080 {
10
+ invoke foo
11
+}
12
+----------
13
+{
14
+ "apps": {
15
+ "http": {
16
+ "servers": {
17
+ "srv0": {
18
+ "listen": [
19
+ ":9080"
20
+ ],
21
+ "routes": [
22
+ {
23
+ "match": [
24
+ {
25
+ "host": [
26
+ "localhost"
27
+ ]
28
+ }
29
+ ],
30
+ "handle": [
31
+ {
32
+ "handler": "subroute",
33
+ "routes": [
34
+ {
35
+ "handle": [
36
+ {
37
+ "handler": "invoke",
38
+ "name": "foo"
39
+ }
40
+ ]
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "terminal": true
46
+ }
47
+ ],
48
+ "named_routes": {
49
+ "foo": {
50
+ "handle": [
51
+ {
52
+ "body": "from snippet",
53
+ "handler": "static_response"
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+}
caddytest/integration/caddyfile_adapt/import_inside_named_route_args.caddyfiletest
new
+62
@@ -0,0 +1,62 @@
1
+(common) {
2
+ respond "{args.0}"
3
+}
4
+
5
+&(foo) {
6
+ import common "hello"
7
+}
8
+
9
+localhost:9080 {
10
+ invoke foo
11
+}
12
+----------
13
+{
14
+ "apps": {
15
+ "http": {
16
+ "servers": {
17
+ "srv0": {
18
+ "listen": [
19
+ ":9080"
20
+ ],
21
+ "routes": [
22
+ {
23
+ "match": [
24
+ {
25
+ "host": [
26
+ "localhost"
27
+ ]
28
+ }
29
+ ],
30
+ "handle": [
31
+ {
32
+ "handler": "subroute",
33
+ "routes": [
34
+ {
35
+ "handle": [
36
+ {
37
+ "handler": "invoke",
38
+ "name": "foo"
39
+ }
40
+ ]
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "terminal": true
46
+ }
47
+ ],
48
+ "named_routes": {
49
+ "foo": {
50
+ "handle": [
51
+ {
52
+ "body": "hello",
53
+ "handler": "static_response"
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+}
caddytest/integration/caddyfile_adapt/import_inside_named_route_block.caddyfiletest
new
+89
@@ -0,0 +1,89 @@
1
+(common) {
2
+ route /api/* {
3
+ respond "api"
4
+ }
5
+}
6
+
7
+&(foo) {
8
+ import common
9
+}
10
+
11
+localhost:9080 {
12
+ invoke foo
13
+}
14
+----------
15
+{
16
+ "apps": {
17
+ "http": {
18
+ "servers": {
19
+ "srv0": {
20
+ "listen": [
21
+ ":9080"
22
+ ],
23
+ "routes": [
24
+ {
25
+ "match": [
26
+ {
27
+ "host": [
28
+ "localhost"
29
+ ]
30
+ }
31
+ ],
32
+ "handle": [
33
+ {
34
+ "handler": "subroute",
35
+ "routes": [
36
+ {
37
+ "handle": [
38
+ {
39
+ "handler": "invoke",
40
+ "name": "foo"
41
+ }
42
+ ]
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "terminal": true
48
+ }
49
+ ],
50
+ "named_routes": {
51
+ "foo": {
52
+ "handle": [
53
+ {
54
+ "handler": "subroute",
55
+ "routes": [
56
+ {
57
+ "handle": [
58
+ {
59
+ "handler": "subroute",
60
+ "routes": [
61
+ {
62
+ "handle": [
63
+ {
64
+ "body": "api",
65
+ "handler": "static_response"
66
+ }
67
+ ]
68
+ }
69
+ ]
70
+ }
71
+ ],
72
+ "match": [
73
+ {
74
+ "path": [
75
+ "/api/*"
76
+ ]
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+}
caddytest/integration/caddyfile_adapt/import_inside_named_route_nested.caddyfiletest
new
+66
@@ -0,0 +1,66 @@
1
+(inner) {
2
+ respond "inner"
3
+}
4
+
5
+(outer) {
6
+ import inner
7
+}
8
+
9
+&(foo) {
10
+ import outer
11
+}
12
+
13
+localhost:9080 {
14
+ invoke foo
15
+}
16
+----------
17
+{
18
+ "apps": {
19
+ "http": {
20
+ "servers": {
21
+ "srv0": {
22
+ "listen": [
23
+ ":9080"
24
+ ],
25
+ "routes": [
26
+ {
27
+ "match": [
28
+ {
29
+ "host": [
30
+ "localhost"
31
+ ]
32
+ }
33
+ ],
34
+ "handle": [
35
+ {
36
+ "handler": "subroute",
37
+ "routes": [
38
+ {
39
+ "handle": [
40
+ {
41
+ "handler": "invoke",
42
+ "name": "foo"
43
+ }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ ],
49
+ "terminal": true
50
+ }
51
+ ],
52
+ "named_routes": {
53
+ "foo": {
54
+ "handle": [
55
+ {
56
+ "body": "inner",
57
+ "handler": "static_response"
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+}