@marcel91200 / caddy Mirror · read-only / commits / c1645c54
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 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 +}