mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-13 09:06:41 +00:00
Merge f1113c0403 into 0fab9f0f7d
This commit is contained in:
commit
b9b6dcc973
3 changed files with 379 additions and 4 deletions
|
|
@ -18,13 +18,14 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Parse parses the input just enough to group tokens, in
|
||||
|
|
@ -170,11 +171,24 @@ func (p *parser) begin() error {
|
|||
|
||||
// get all the tokens from the block, including the braces
|
||||
tokens, err := p.blockTokens(true)
|
||||
tokens = append([]Token{nameToken}, tokens...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokens = append([]Token{nameToken}, tokens...)
|
||||
p.block.Segments = []Segment{tokens}
|
||||
|
||||
// Parse the whole block, to evaluate all imports and
|
||||
// other potential parse-time constructs at parse time,
|
||||
// then use result as the block definition
|
||||
routeParser := p.childParser(tokens)
|
||||
if err := routeParser.parseOne(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy parsed segment tokens back along with any changes
|
||||
// to the import graph, in case it was modified.
|
||||
p.block.Segments = []Segment{routeParser.tokens}
|
||||
p.importGraph = routeParser.importGraph
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -737,6 +751,24 @@ func (p *parser) isSnippet() (bool, string) {
|
|||
return false, ""
|
||||
}
|
||||
|
||||
func (p *parser) childParser(tokens []Token) parser {
|
||||
nodes := maps.Clone(p.importGraph.nodes)
|
||||
edges := maps.Clone(p.importGraph.edges)
|
||||
|
||||
context := maps.Clone(p.context)
|
||||
snippets := maps.Clone(p.definedSnippets)
|
||||
|
||||
return parser{
|
||||
Dispenser: &Dispenser{tokens: tokens, context: context},
|
||||
definedSnippets: snippets,
|
||||
importGraph: importGraph{
|
||||
nodes: nodes,
|
||||
edges: edges,
|
||||
},
|
||||
nesting: p.Nesting(),
|
||||
}
|
||||
}
|
||||
|
||||
// read and store everything in a block for later replay.
|
||||
func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
|
||||
// block must have curlies.
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@ import (
|
|||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseVariadic(t *testing.T) {
|
||||
|
|
@ -910,6 +914,129 @@ func TestRejectAnonymousImportBlock(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAcceptImportWithinInvoke(t *testing.T) {
|
||||
p := testParser(`
|
||||
(proxy) {
|
||||
reverse_proxy {args[:]}
|
||||
}
|
||||
|
||||
&(named) {
|
||||
import proxy 192.168.1.1:80
|
||||
}
|
||||
|
||||
site {
|
||||
invoke named
|
||||
}
|
||||
`)
|
||||
|
||||
blocks, err := p.parseAll()
|
||||
if err != nil {
|
||||
t.Errorf("Expected error to be nil but got '%v'", err)
|
||||
}
|
||||
|
||||
keys := make([]string, 0)
|
||||
var namedBlock TestServerBlock
|
||||
|
||||
for _, block := range blocks {
|
||||
blockKeys := block.GetKeysText()
|
||||
|
||||
if slices.Contains(blockKeys, "named") {
|
||||
namedBlock = testServerBlock(block)
|
||||
}
|
||||
|
||||
keys = slices.Concat(keys, blockKeys)
|
||||
}
|
||||
|
||||
assert.Equal(t, keys, []string{"named", "site"})
|
||||
assert.True(t, namedBlock.HasBraces)
|
||||
|
||||
snippet := p.definedSnippets["proxy"]
|
||||
blockTokens := namedBlock.InnerTokens
|
||||
assert.Equalf(
|
||||
t, len(snippet), len(blockTokens),
|
||||
"Token mismatch, snippet has %d tokens while the named route ends up with %d",
|
||||
len(snippet), len(blockTokens),
|
||||
)
|
||||
|
||||
placeholderRegexp := regexp.MustCompile("\\{.+}")
|
||||
for idx, tok := range blockTokens {
|
||||
assert.Equal(t, tok.snippetName, "proxy")
|
||||
|
||||
isPlaceholder := placeholderRegexp.MatchString(snippet[idx].Text)
|
||||
if !isPlaceholder {
|
||||
assert.Equal(t, tok.Text, snippet[idx].Text)
|
||||
} else {
|
||||
assert.NotRegexpf(
|
||||
t, placeholderRegexp, tok.Text,
|
||||
"Imported tokens still include a placeholder: %s", tok.Text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplexImportInvokeConfig(t *testing.T) {
|
||||
p := testParser(`
|
||||
(nesting_further) {
|
||||
do something
|
||||
}
|
||||
|
||||
(nesting) {
|
||||
directive again with more {
|
||||
interesting = content
|
||||
import nesting_further
|
||||
}
|
||||
}
|
||||
|
||||
(proxy) {
|
||||
reverse_proxy {args[:]}
|
||||
import nesting
|
||||
}
|
||||
|
||||
&(named) {
|
||||
import proxy 192.168.1.1:80
|
||||
|
||||
handle_error {
|
||||
import nesting_further
|
||||
respond 404
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
blocks, err := p.parseAll()
|
||||
if err != nil {
|
||||
t.Errorf("Expected error to be nil but got '%v'", err)
|
||||
}
|
||||
|
||||
assert.Len(t, blocks, 1, "Expected only the named route to be in blocks")
|
||||
assert.Equalf(
|
||||
t, blocks[0].GetKeysText(), []string{"named"},
|
||||
"Block in result is not the named route, expected name 'named' got: %s",
|
||||
strings.Join(blocks[0].GetKeysText(), ", "),
|
||||
)
|
||||
|
||||
namedBlock := testServerBlock(blocks[0])
|
||||
blockText := stringifyTokens(namedBlock.InnerTokens)
|
||||
deeplyNestedImport := stringifyTokens(p.definedSnippets["nesting_further"])
|
||||
nestedImport := slices.Concat(
|
||||
[]string{"directive", "again", "with", "more", "{", "interesting", "=", "content"},
|
||||
deeplyNestedImport,
|
||||
[]string{"}"},
|
||||
)
|
||||
proxyImport := slices.Concat(
|
||||
[]string{"reverse_proxy", "192.168.1.1:80"},
|
||||
nestedImport,
|
||||
)
|
||||
|
||||
expectedText := slices.Concat(
|
||||
proxyImport,
|
||||
[]string{"handle_error", "{"},
|
||||
deeplyNestedImport,
|
||||
[]string{"respond", "404", "}"},
|
||||
)
|
||||
|
||||
assert.ElementsMatch(t, blockText, expectedText)
|
||||
}
|
||||
|
||||
func TestAcceptSiteImportWithBraces(t *testing.T) {
|
||||
p := testParser(`
|
||||
(site) {
|
||||
|
|
@ -1034,3 +1161,39 @@ func TestImportedSnippetDefinitionRetainsBlockPlaceholder(t *testing.T) {
|
|||
func testParser(input string) parser {
|
||||
return parser{Dispenser: NewTestDispenser(input)}
|
||||
}
|
||||
|
||||
type TestServerBlock struct {
|
||||
ServerBlock
|
||||
|
||||
InnerTokens []Token
|
||||
}
|
||||
|
||||
func stringifyTokens(tokens []Token) []string {
|
||||
return slices.Collect(func(yield func(string) bool) {
|
||||
for _, tok := range tokens {
|
||||
if !yield(tok.Text) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testServerBlock(block ServerBlock) TestServerBlock {
|
||||
innerTokens := slices.Collect(func(yield func(Token) bool) {
|
||||
for _, segment := range block.Segments {
|
||||
tokens := []Token(segment)
|
||||
for _, token := range tokens {
|
||||
if !yield(token) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
keyLength := len(block.Keys)
|
||||
return TestServerBlock{
|
||||
ServerBlock: block,
|
||||
// Exclude keys, opening brace and closing brace
|
||||
InnerTokens: innerTokens[keyLength+1 : len(innerTokens)-1],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
(snippet) {
|
||||
handle {
|
||||
invoke first
|
||||
}
|
||||
|
||||
respond "snippet"
|
||||
}
|
||||
|
||||
&(first) {
|
||||
@first path /first
|
||||
vars @first first 1
|
||||
respond "first"
|
||||
}
|
||||
|
||||
&(second) {
|
||||
import snippet
|
||||
}
|
||||
|
||||
:8881 {
|
||||
invoke first
|
||||
route {
|
||||
invoke second
|
||||
}
|
||||
}
|
||||
|
||||
:8882 {
|
||||
handle {
|
||||
invoke second
|
||||
}
|
||||
}
|
||||
|
||||
:8883 {
|
||||
respond "no invoke"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "first"
|
||||
},
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"named_routes": {
|
||||
"first": {
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"first": 1,
|
||||
"handler": "vars"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/first"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "first",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"second": {
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "first"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
":8882"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "second"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"named_routes": {
|
||||
"second": {
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "invoke",
|
||||
"name": "first"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"srv2": {
|
||||
"listen": [
|
||||
":8883"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "no invoke",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue