Refactor Caddyfile adapter and module registration

Use piles from which to draw config values.

Module values can return their name, so now we can do two-way mapping
from value to name and name to value; whereas before we could only map
name to value. This was problematic with the Caddyfile adapter since
it receives values and needs to know the name to put in the config.
This commit is contained in:
Matthew Holt 2019-08-21 10:46:35 -06:00
parent c4159ef76d
commit c9980fd367
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
46 changed files with 1512 additions and 1105 deletions

View File

@ -95,9 +95,9 @@ func StartAdmin(initialConfigJSON []byte) error {
mux.HandleFunc("/debug/pprof/trace", pprof.Trace) mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
///// END PPROF STUFF ////// ///// END PPROF STUFF //////
for _, m := range GetModules("admin") { for _, m := range GetModules("admin.routers") {
routes := m.New().([]AdminRoute) adminrtr := m.New().(AdminRouter)
for _, route := range routes { for _, route := range adminrtr.Routes() {
mux.Handle(route.Pattern, route) mux.Handle(route.Pattern, route)
} }
} }
@ -146,6 +146,11 @@ func StopAdmin() error {
return nil return nil
} }
// AdminRouter is a type which can return routes for the admin API.
type AdminRouter interface {
Routes() []AdminRoute
}
// AdminRoute represents a route for the admin endpoint. // AdminRoute represents a route for the admin endpoint.
type AdminRoute struct { type AdminRoute struct {
http.Handler http.Handler

View File

@ -37,14 +37,12 @@ func (a Adapter) Adapt(body []byte, options map[string]string) ([]byte, []caddyc
options = make(map[string]string) options = make(map[string]string)
} }
directives := a.ServerType.ValidDirectives()
filename := options["filename"] filename := options["filename"]
if filename == "" { if filename == "" {
filename = "Caddyfile" filename = "Caddyfile"
} }
serverBlocks, err := Parse(filename, bytes.NewReader(body), directives) serverBlocks, err := Parse(filename, bytes.NewReader(body))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -77,10 +75,6 @@ type Unmarshaler interface {
// ServerType is a type that can evaluate a Caddyfile and set up a caddy config. // ServerType is a type that can evaluate a Caddyfile and set up a caddy config.
type ServerType interface { type ServerType interface {
// ValidDirectives returns a list of the
// server type's recognized directives.
ValidDirectives() []string
// Setup takes the server blocks which // Setup takes the server blocks which
// contain tokens, as well as options // contain tokens, as well as options
// (e.g. CLI flags) and creates a Caddy // (e.g. CLI flags) and creates a Caddy

View File

@ -31,6 +31,7 @@ type Dispenser struct {
} }
// NewDispenser returns a Dispenser filled with the given tokens. // NewDispenser returns a Dispenser filled with the given tokens.
// TODO: Get rid of the filename argument; it seems pointless here
func NewDispenser(filename string, tokens []Token) *Dispenser { func NewDispenser(filename string, tokens []Token) *Dispenser {
return &Dispenser{ return &Dispenser{
filename: filename, filename: filename,
@ -51,15 +52,15 @@ func (d *Dispenser) Next() bool {
} }
// Prev moves to the previous token. It does the inverse // Prev moves to the previous token. It does the inverse
// of Next(). Generally, this should only be used in // of Next(), except this function may decrement the cursor
// special cases such as deleting a token from the slice // to -1 so that the next call to Next() points to the
// that d is iterating. In that case, without using Prev(), // first token; this allows dispensing to "start over". This
// the dispenser would be pointing at the wrong token since // method returns true if the cursor ends up pointing to a
// deleting a token implicitly advances the cursor. // valid token.
func (d *Dispenser) Prev() bool { func (d *Dispenser) Prev() bool {
if d.cursor > 0 { if d.cursor > -1 {
d.cursor-- d.cursor--
return true return d.cursor > -1
} }
return false return false
} }
@ -223,8 +224,7 @@ func (d *Dispenser) RemainingArgs() []string {
// "directive" whether that be to the end of the line or // "directive" whether that be to the end of the line or
// the end of a block that starts at the end of the line. // the end of a block that starts at the end of the line.
func (d *Dispenser) NewFromNextTokens() *Dispenser { func (d *Dispenser) NewFromNextTokens() *Dispenser {
var tkns []Token tkns := []Token{d.Token()}
tkns = append(tkns, d.Token())
for d.NextArg() { for d.NextArg() {
tkns = append(tkns, d.Token()) tkns = append(tkns, d.Token())
} }
@ -245,10 +245,14 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
// Token returns the current token. // Token returns the current token.
func (d *Dispenser) Token() Token { func (d *Dispenser) Token() Token {
if d.cursor < 0 || d.cursor >= len(d.tokens) { return d.TokenAt(d.cursor)
}
func (d *Dispenser) TokenAt(cursor int) Token {
if cursor < 0 || cursor >= len(d.tokens) {
return Token{} return Token{}
} }
return d.tokens[d.cursor] return d.tokens[cursor]
} }
// Cursor returns the current cursor (token index). // Cursor returns the current cursor (token index).
@ -256,6 +260,10 @@ func (d *Dispenser) Cursor() int {
return d.cursor return d.cursor
} }
func (d *Dispenser) Reset() {
d.cursor = -1
}
// ArgErr returns an argument error, meaning that another // ArgErr returns an argument error, meaning that another
// argument was expected but not found. In other words, // argument was expected but not found. In other words,
// a line break or open curly brace was encountered instead of // a line break or open curly brace was encountered instead of

View File

@ -28,12 +28,12 @@ import (
// Directives that do not appear in validDirectives will cause // Directives that do not appear in validDirectives will cause
// an error. If you do not want to check for valid directives, // an error. If you do not want to check for valid directives,
// pass in nil instead. // pass in nil instead.
func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) { func Parse(filename string, input io.Reader) ([]ServerBlock, error) {
tokens, err := allTokens(input) tokens, err := allTokens(input)
if err != nil { if err != nil {
return nil, err return nil, err
} }
p := parser{Dispenser: NewDispenser(filename, tokens), validDirectives: validDirectives} p := parser{Dispenser: NewDispenser(filename, tokens)}
return p.parseAll() return p.parseAll()
} }
@ -56,9 +56,9 @@ func allTokens(input io.Reader) ([]Token, error) {
type parser struct { type parser struct {
*Dispenser *Dispenser
block ServerBlock // current server block being parsed block ServerBlock // current server block being parsed
validDirectives []string // a directive must be valid or it's an error
eof bool // if we encounter a valid EOF in a hard place eof bool // if we encounter a valid EOF in a hard place
definedSnippets map[string][]Token definedSnippets map[string][]Token
nesting int
} }
func (p *parser) parseAll() ([]ServerBlock, error) { func (p *parser) parseAll() ([]ServerBlock, error) {
@ -72,14 +72,16 @@ func (p *parser) parseAll() ([]ServerBlock, error) {
if len(p.block.Keys) > 0 { if len(p.block.Keys) > 0 {
blocks = append(blocks, p.block) blocks = append(blocks, p.block)
} }
if p.nesting > 0 {
return blocks, p.EOFErr()
}
} }
return blocks, nil return blocks, nil
} }
func (p *parser) parseOne() error { func (p *parser) parseOne() error {
p.block = ServerBlock{Tokens: make(map[string][]Token)} p.block = ServerBlock{}
return p.begin() return p.begin()
} }
@ -186,7 +188,7 @@ func (p *parser) blockContents() error {
return err return err
} }
// Only look for close curly brace if there was an opening // only look for close curly brace if there was an opening
if errOpenCurlyBrace == nil { if errOpenCurlyBrace == nil {
err = p.closeCurlyBrace() err = p.closeCurlyBrace()
if err != nil { if err != nil {
@ -205,6 +207,7 @@ func (p *parser) directives() error {
for p.Next() { for p.Next() {
// end of server block // end of server block
if p.Val() == "}" { if p.Val() == "}" {
// p.nesting has already been decremented
break break
} }
@ -218,11 +221,15 @@ func (p *parser) directives() error {
continue continue
} }
// normal case: parse a directive on this line // normal case: parse a directive as a new segment
// (a "segment" is a line which starts with a directive
// and which ends at the end of the line or at the end of
// the block that is opened at the end of the line)
if err := p.directive(); err != nil { if err := p.directive(); err != nil {
return err return err
} }
} }
return nil return nil
} }
@ -345,25 +352,24 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// are loaded into the current server block for later use // are loaded into the current server block for later use
// by directive setup functions. // by directive setup functions.
func (p *parser) directive() error { func (p *parser) directive() error {
dir := replaceEnvVars(p.Val()) // evaluate any env vars in directive token
nesting := 0 p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
if !p.validDirective(dir) { // a segment is a list of tokens associated with this directive
return p.Errf("Unknown directive '%s'", dir) var segment Segment
}
// The directive itself is appended as a relevant token // the directive itself is appended as a relevant token
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) segment = append(segment, p.Token())
for p.Next() { for p.Next() {
if p.Val() == "{" { if p.Val() == "{" {
nesting++ p.nesting++
} else if p.isNewLine() && nesting == 0 { } else if p.isNewLine() && p.nesting == 0 {
p.cursor-- // read too far p.cursor-- // read too far
break break
} else if p.Val() == "}" && nesting > 0 { } else if p.Val() == "}" && p.nesting > 0 {
nesting-- p.nesting--
} else if p.Val() == "}" && nesting == 0 { } else if p.Val() == "}" && p.nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace") return p.Err("Unexpected '}' because no matching opening brace")
} else if p.Val() == "import" && p.isNewLine() { } else if p.Val() == "import" && p.isNewLine() {
if err := p.doImport(); err != nil { if err := p.doImport(); err != nil {
@ -373,12 +379,15 @@ func (p *parser) directive() error {
continue continue
} }
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text) p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) segment = append(segment, p.Token())
} }
if nesting > 0 { p.block.Segments = append(p.block.Segments, segment)
if p.nesting > 0 {
return p.EOFErr() return p.EOFErr()
} }
return nil return nil
} }
@ -404,19 +413,6 @@ func (p *parser) closeCurlyBrace() error {
return nil return nil
} }
// validDirective returns true if dir is in p.validDirectives.
func (p *parser) validDirective(dir string) bool {
if p.validDirectives == nil {
return true
}
for _, d := range p.validDirectives {
if d == dir {
return true
}
}
return false
}
// replaceEnvVars replaces environment variables that appear in the token // replaceEnvVars replaces environment variables that appear in the token
// and understands both the $UNIX and %WINDOWS% syntaxes. // and understands both the $UNIX and %WINDOWS% syntaxes.
func replaceEnvVars(s string) string { func replaceEnvVars(s string) string {
@ -447,13 +443,6 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
return s return s
} }
// ServerBlock associates any number of keys (usually addresses
// of some sort) with tokens (grouped by directive name).
type ServerBlock struct {
Keys []string
Tokens map[string][]Token
}
func (p *parser) isSnippet() (bool, string) { func (p *parser) isSnippet() (bool, string) {
keys := p.block.Keys keys := p.block.Keys
// A snippet block is a single key with parens. Nothing else qualifies. // A snippet block is a single key with parens. Nothing else qualifies.
@ -480,6 +469,7 @@ func (p *parser) snippetTokens() ([]Token, error) {
} }
} }
if p.Val() == "{" { if p.Val() == "{" {
p.nesting++
count++ count++
} }
tokens = append(tokens, p.tokens[p.cursor]) tokens = append(tokens, p.tokens[p.cursor])
@ -490,3 +480,43 @@ func (p *parser) snippetTokens() ([]Token, error) {
} }
return tokens, nil return tokens, nil
} }
// ServerBlock associates any number of keys from the
// head of the server block with tokens, which are
// grouped by segments.
type ServerBlock struct {
Keys []string
Segments []Segment
}
// DispenseDirective returns a dispenser that contains
// all the tokens in the server block.
func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
var tokens []Token
for _, seg := range sb.Segments {
if len(seg) > 0 && seg[0].Text == dir {
tokens = append(tokens, seg...)
}
}
return NewDispenser("", tokens)
}
// Segment is a list of tokens which begins with a directive
// and ends at the end of the directive (either at the end of
// the line, or at the end of a block it opens).
type Segment []Token
// Directive returns the directive name for the segment.
// The directive name is the text of the first token.
func (s Segment) Directive() string {
if len(s) > 0 {
return s[0].Text
}
return ""
}
// NewDispenser returns a dispenser for this
// segment's tokens.
func (s Segment) NewDispenser() *Dispenser {
return NewDispenser("", s)
}

View File

@ -22,6 +22,8 @@ import (
"testing" "testing"
) )
// TODO: re-enable all tests
func TestAllTokens(t *testing.T) { func TestAllTokens(t *testing.T) {
input := strings.NewReader("a b c\nd e") input := strings.NewReader("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"} expected := []string{"a", "b", "c", "d", "e"}
@ -53,84 +55,67 @@ func TestParseOneAndImport(t *testing.T) {
input string input string
shouldErr bool shouldErr bool
keys []string keys []string
tokens map[string]int // map of directive name to number of tokens expected numTokens []int // number of tokens to expect in each segment
}{ }{
{`localhost`, false, []string{ {`localhost`, false, []string{
"localhost", "localhost",
}, map[string]int{}}, }, []int{}},
{`localhost {`localhost
dir1`, false, []string{ dir1`, false, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{1}},
"dir1": 1,
}},
{`localhost:1234 {`localhost:1234
dir1 foo bar`, false, []string{ dir1 foo bar`, false, []string{
"localhost:1234", "localhost:1234",
}, map[string]int{ }, []int{3},
"dir1": 3, },
}},
{`localhost { {`localhost {
dir1 dir1
}`, false, []string{ }`, false, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{1}},
"dir1": 1,
}},
{`localhost:1234 { {`localhost:1234 {
dir1 foo bar dir1 foo bar
dir2 dir2
}`, false, []string{ }`, false, []string{
"localhost:1234", "localhost:1234",
}, map[string]int{ }, []int{3, 1}},
"dir1": 3,
"dir2": 1,
}},
{`http://localhost https://localhost {`http://localhost https://localhost
dir1 foo bar`, false, []string{ dir1 foo bar`, false, []string{
"http://localhost", "http://localhost",
"https://localhost", "https://localhost",
}, map[string]int{ }, []int{3}},
"dir1": 3,
}},
{`http://localhost https://localhost { {`http://localhost https://localhost {
dir1 foo bar dir1 foo bar
}`, false, []string{ }`, false, []string{
"http://localhost", "http://localhost",
"https://localhost", "https://localhost",
}, map[string]int{ }, []int{3}},
"dir1": 3,
}},
{`http://localhost, https://localhost { {`http://localhost, https://localhost {
dir1 foo bar dir1 foo bar
}`, false, []string{ }`, false, []string{
"http://localhost", "http://localhost",
"https://localhost", "https://localhost",
}, map[string]int{ }, []int{3}},
"dir1": 3,
}},
{`http://localhost, { {`http://localhost, {
}`, true, []string{ }`, true, []string{
"http://localhost", "http://localhost",
}, map[string]int{}}, }, []int{}},
{`host1:80, http://host2.com {`host1:80, http://host2.com
dir1 foo bar dir1 foo bar
dir2 baz`, false, []string{ dir2 baz`, false, []string{
"host1:80", "host1:80",
"http://host2.com", "http://host2.com",
}, map[string]int{ }, []int{3, 2}},
"dir1": 3,
"dir2": 2,
}},
{`http://host1.com, {`http://host1.com,
http://host2.com, http://host2.com,
@ -138,7 +123,7 @@ func TestParseOneAndImport(t *testing.T) {
"http://host1.com", "http://host1.com",
"http://host2.com", "http://host2.com",
"https://host3.com", "https://host3.com",
}, map[string]int{}}, }, []int{}},
{`http://host1.com:1234, https://host2.com {`http://host1.com:1234, https://host2.com
dir1 foo { dir1 foo {
@ -147,10 +132,7 @@ func TestParseOneAndImport(t *testing.T) {
dir2`, false, []string{ dir2`, false, []string{
"http://host1.com:1234", "http://host1.com:1234",
"https://host2.com", "https://host2.com",
}, map[string]int{ }, []int{6, 1}},
"dir1": 6,
"dir2": 1,
}},
{`127.0.0.1 {`127.0.0.1
dir1 { dir1 {
@ -160,34 +142,25 @@ func TestParseOneAndImport(t *testing.T) {
foo bar foo bar
}`, false, []string{ }`, false, []string{
"127.0.0.1", "127.0.0.1",
}, map[string]int{ }, []int{5, 5}},
"dir1": 5,
"dir2": 5,
}},
{`localhost {`localhost
dir1 { dir1 {
foo`, true, []string{ foo`, true, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{3}},
"dir1": 3,
}},
{`localhost {`localhost
dir1 { dir1 {
}`, false, []string{ }`, false, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{3}},
"dir1": 3,
}},
{`localhost {`localhost
dir1 { dir1 {
} }`, true, []string{ } }`, true, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{}},
"dir1": 3,
}},
{`localhost {`localhost
dir1 { dir1 {
@ -197,48 +170,38 @@ func TestParseOneAndImport(t *testing.T) {
} }
dir2 foo bar`, false, []string{ dir2 foo bar`, false, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{7, 3}},
"dir1": 7,
"dir2": 3,
}},
{``, false, []string{}, map[string]int{}}, {``, false, []string{}, []int{}},
{`localhost {`localhost
dir1 arg1 dir1 arg1
import testdata/import_test1.txt`, false, []string{ import testdata/import_test1.txt`, false, []string{
"localhost", "localhost",
}, map[string]int{ }, []int{2, 3, 1}},
"dir1": 2,
"dir2": 3,
"dir3": 1,
}},
{`import testdata/import_test2.txt`, false, []string{ {`import testdata/import_test2.txt`, false, []string{
"host1", "host1",
}, map[string]int{ }, []int{1, 2}},
"dir1": 1,
"dir2": 2,
}},
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}}, {`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, []int{}},
{`import testdata/not_found.txt`, true, []string{}, map[string]int{}}, {`import testdata/not_found.txt`, true, []string{}, []int{}},
{`""`, false, []string{}, map[string]int{}}, {`""`, false, []string{}, []int{}},
{``, false, []string{}, map[string]int{}}, {``, false, []string{}, []int{}},
// test cases found by fuzzing! // test cases found by fuzzing!
{`import }{$"`, true, []string{}, map[string]int{}}, {`import }{$"`, true, []string{}, []int{}},
{`import /*/*.txt`, true, []string{}, map[string]int{}}, {`import /*/*.txt`, true, []string{}, []int{}},
{`import /???/?*?o`, true, []string{}, map[string]int{}}, {`import /???/?*?o`, true, []string{}, []int{}},
{`import /??`, true, []string{}, map[string]int{}}, {`import /??`, true, []string{}, []int{}},
{`import /[a-z]`, true, []string{}, map[string]int{}}, {`import /[a-z]`, true, []string{}, []int{}},
{`import {$}`, true, []string{}, map[string]int{}}, {`import {$}`, true, []string{}, []int{}},
{`import {%}`, true, []string{}, map[string]int{}}, {`import {%}`, true, []string{}, []int{}},
{`import {$$}`, true, []string{}, map[string]int{}}, {`import {$$}`, true, []string{}, []int{}},
{`import {%%}`, true, []string{}, map[string]int{}}, {`import {%%}`, true, []string{}, []int{}},
} { } {
result, err := testParseOne(test.input) result, err := testParseOne(test.input)
@ -261,15 +224,16 @@ func TestParseOneAndImport(t *testing.T) {
} }
} }
if len(result.Tokens) != len(test.tokens) { if len(result.Segments) != len(test.numTokens) {
t.Errorf("Test %d: Expected %d directives, had %d", t.Errorf("Test %d: Expected %d segments, had %d",
i, len(test.tokens), len(result.Tokens)) i, len(test.numTokens), len(result.Segments))
continue continue
} }
for directive, tokens := range result.Tokens {
if len(tokens) != test.tokens[directive] { for j, seg := range result.Segments {
t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d", if len(seg) != test.numTokens[j] {
i, directive, test.tokens[directive], len(tokens)) t.Errorf("Test %d, segment %d: Expected %d tokens, counted %d",
i, j, test.numTokens[j], len(seg))
continue continue
} }
} }
@ -289,12 +253,12 @@ func TestRecursiveImport(t *testing.T) {
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys) t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
return false return false
} }
if len(got.Tokens) != 2 { if len(got.Segments) != 2 {
t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens)) t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false return false
} }
if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 { if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
t.Errorf("got unexpect tokens: %v", got.Tokens) t.Errorf("got unexpect tokens: %v", got.Segments)
return false return false
} }
return true return true
@ -384,12 +348,12 @@ func TestDirectiveImport(t *testing.T) {
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys) t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
return false return false
} }
if len(got.Tokens) != 2 { if len(got.Segments) != 2 {
t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens)) t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false return false
} }
if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 { if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
t.Errorf("got unexpect tokens: %v", got.Tokens) t.Errorf("got unexpect tokens: %v", got.Segments)
return false return false
} }
return true return true
@ -557,21 +521,21 @@ func TestEnvironmentReplacement(t *testing.T) {
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual { if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
} }
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual { if actual, expected := blocks[0].Segments[0][1].Text, "foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
// combined windows env vars in argument // combined windows env vars in argument
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}") p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
blocks, _ = p.parseAll() blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual { if actual, expected := blocks[0].Segments[0][1].Text, "servername.com/foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
// malformed env var (windows) // malformed env var (windows)
p = testParser(":1234\ndir1 {%ADDRESS}") p = testParser(":1234\ndir1 {%ADDRESS}")
blocks, _ = p.parseAll() blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual { if actual, expected := blocks[0].Segments[0][1].Text, "{%ADDRESS}"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual) t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
} }
@ -585,22 +549,18 @@ func TestEnvironmentReplacement(t *testing.T) {
// in quoted field // in quoted field
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"") p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
blocks, _ = p.parseAll() blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual { if actual, expected := blocks[0].Segments[0][1].Text, "Test foobar test"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
// after end token // after end token
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"") p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
blocks, _ = p.parseAll() blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual { if actual, expected := blocks[0].Segments[0][1].Text, "{{ .Name }} foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
} }
func testParser(input string) parser {
return parser{Dispenser: newTestDispenser(input)}
}
func TestSnippets(t *testing.T) { func TestSnippets(t *testing.T) {
p := testParser(` p := testParser(`
(common) { (common) {
@ -617,7 +577,7 @@ func TestSnippets(t *testing.T) {
} }
for _, b := range blocks { for _, b := range blocks {
t.Log(b.Keys) t.Log(b.Keys)
t.Log(b.Tokens) t.Log(b.Segments)
} }
if len(blocks) != 1 { if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks)) t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
@ -625,16 +585,15 @@ func TestSnippets(t *testing.T) {
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual { if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual) t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
} }
if len(blocks[0].Tokens) != 2 { if len(blocks[0].Segments) != 2 {
t.Fatalf("Server block should have tokens from import") t.Fatalf("Server block should have tokens from import, got: %+v", blocks[0])
} }
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual { if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual { if actual, expected := blocks[0].Segments[1][1].Text, "stderr"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
} }
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) { func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
@ -666,9 +625,9 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
} }
for _, b := range blocks { for _, b := range blocks {
t.Log(b.Keys) t.Log(b.Keys)
t.Log(b.Tokens) t.Log(b.Segments)
} }
auth := blocks[0].Tokens["basicauth"] auth := blocks[0].Segments[0]
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
if line != "basicauth / import password" { if line != "basicauth / import password" {
// Previously, it would be changed to: // Previously, it would be changed to:
@ -701,7 +660,7 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
} }
for _, b := range blocks { for _, b := range blocks {
t.Log(b.Keys) t.Log(b.Keys)
t.Log(b.Tokens) t.Log(b.Segments)
} }
if len(blocks) != 1 { if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks)) t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
@ -709,10 +668,14 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual { if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual) t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
} }
if len(blocks[0].Tokens) != 1 { if len(blocks[0].Segments) != 1 {
t.Fatalf("Server block should have tokens from import") t.Fatalf("Server block should have tokens from import")
} }
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual { if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
} }
} }
func testParser(input string) parser {
return parser{Dispenser: newTestDispenser(input)}
}

View File

@ -22,7 +22,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/mholt/certmagic" "github.com/mholt/certmagic"
) )
@ -73,8 +73,8 @@ import (
// repetition may be undesirable, so call consolidateAddrMappings() to map // repetition may be undesirable, so call consolidateAddrMappings() to map
// multiple addresses to the same lists of server blocks (a many:many mapping). // multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.) // (Doing this is essentially a map-reduce technique.)
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.ServerBlock) (map[string][]caddyfile.ServerBlock, error) { func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) {
sbmap := make(map[string][]caddyfile.ServerBlock) sbmap := make(map[string][]serverBlock)
for i, sblock := range originalServerBlocks { for i, sblock := range originalServerBlocks {
// within a server block, we need to map all the listener addresses // within a server block, we need to map all the listener addresses
@ -83,7 +83,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
// key of a server block as its own, but without having to repeat its // key of a server block as its own, but without having to repeat its
// contents in cases where multiple keys really can be served together // contents in cases where multiple keys really can be served together
addrToKeys := make(map[string][]string) addrToKeys := make(map[string][]string)
for j, key := range sblock.Keys { for j, key := range sblock.block.Keys {
// a key can have multiple listener addresses if there are multiple // a key can have multiple listener addresses if there are multiple
// arguments to the 'bind' directive (although they will all have // arguments to the 'bind' directive (although they will all have
// the same port, since the port is defined by the key or is implicit // the same port, since the port is defined by the key or is implicit
@ -105,9 +105,12 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
// server block are only the ones which use the address; but // server block are only the ones which use the address; but
// the contents (tokens) are of course the same // the contents (tokens) are of course the same
for addr, keys := range addrToKeys { for addr, keys := range addrToKeys {
sbmap[addr] = append(sbmap[addr], caddyfile.ServerBlock{ sbmap[addr] = append(sbmap[addr], serverBlock{
Keys: keys, block: caddyfile.ServerBlock{
Tokens: sblock.Tokens, Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
}) })
} }
} }
@ -123,7 +126,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each // entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
// association from multiple addresses to multiple server blocks; i.e. each element of // association from multiple addresses to multiple server blocks; i.e. each element of
// the returned slice) becomes a server definition in the output JSON. // the returned slice) becomes a server definition in the output JSON.
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]caddyfile.ServerBlock) []sbAddrAssociation { func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
var sbaddrs []sbAddrAssociation var sbaddrs []sbAddrAssociation
for addr, sblocks := range addrToServerBlocks { for addr, sblocks := range addrToServerBlocks {
// we start with knowing that at least this address // we start with knowing that at least this address
@ -151,11 +154,12 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]ca
return sbaddrs return sbaddrs
} }
func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBlock, key string) ([]string, error) { func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) {
addr, err := standardizeAddress(key) addr, err := ParseAddress(key)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing key: %v", err) return nil, fmt.Errorf("parsing key: %v", err)
} }
addr = addr.Normalize()
lnPort := defaultPort lnPort := defaultPort
if addr.Port != "" { if addr.Port != "" {
@ -168,11 +172,8 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBloc
// the bind directive specifies hosts, but is optional // the bind directive specifies hosts, but is optional
var lnHosts []string var lnHosts []string
for i, token := range sblock.Tokens["bind"] { for _, cfgVal := range sblock.pile["bind"] {
if i == 0 { lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
continue
}
lnHosts = append(lnHosts, token.Text)
} }
if len(lnHosts) == 0 { if len(lnHosts) == 0 {
lnHosts = []string{""} lnHosts = []string{""}
@ -205,7 +206,53 @@ type Address struct {
Original, Scheme, Host, Port, Path string Original, Scheme, Host, Port, Path string
} }
// String returns a human-friendly print of the address. // ParseAddress parses an address string into a structured format with separate
// scheme, host, port, and path portions, as well as the original input string.
func ParseAddress(str string) (Address, error) {
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
input := str
// Split input into components (prepend with // to force host portion by default)
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
str = "//" + str
}
u, err := url.Parse(str)
if err != nil {
return Address{}, err
}
// separate host and port
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
host, port, err = net.SplitHostPort(u.Host + ":")
if err != nil {
host = u.Host
}
}
// see if we can set port based off scheme
if port == "" {
if u.Scheme == "http" {
port = httpPort
} else if u.Scheme == "https" {
port = httpsPort
}
}
// error if scheme and port combination violate convention
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
}
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
}
// TODO: which of the methods on Address are even used?
// String returns a human-readable form of a. It will
// be a cleaned-up and filled-out URL string.
func (a Address) String() string { func (a Address) String() string {
if a.Host == "" && a.Port == "" { if a.Host == "" && a.Port == "" {
return "" return ""
@ -235,16 +282,7 @@ func (a Address) String() string {
return s return s
} }
// VHost returns a sensible concatenation of Host:Port/Path from a. // Normalize returns a normalized version of a.
// It's basically the a.Original but without the scheme.
func (a Address) VHost() string {
if idx := strings.Index(a.Original, "://"); idx > -1 {
return a.Original[idx+3:]
}
return a.Original
}
// Normalize normalizes URL: turn scheme and host names into lower case
func (a Address) Normalize() Address { func (a Address) Normalize() Address {
path := a.Path path := a.Path
if !caseSensitivePath { if !caseSensitivePath {
@ -266,8 +304,8 @@ func (a Address) Normalize() Address {
} }
} }
// Key is similar to String, just replaces scheme and host values with modified values. // Key returns a string form of a, much like String() does, but this
// Unlike String it doesn't add anything default (scheme, port, etc) // method doesn't add anything default that wasn't in the original.
func (a Address) Key() string { func (a Address) Key() string {
res := "" res := ""
if a.Scheme != "" { if a.Scheme != "" {
@ -276,11 +314,11 @@ func (a Address) Key() string {
if a.Host != "" { if a.Host != "" {
res += a.Host res += a.Host
} }
if a.Port != "" { // insert port only if the original has its own explicit port
if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) { if a.Port != "" &&
// insert port only if the original has its own explicit port len(a.Original) >= len(res) &&
res += ":" + a.Port strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
} res += ":" + a.Port
} }
if a.Path != "" { if a.Path != "" {
res += a.Path res += a.Path
@ -288,63 +326,7 @@ func (a Address) Key() string {
return res return res
} }
// standardizeAddress parses an address string into a structured format with separate
// scheme, host, port, and path portions, as well as the original input string.
func standardizeAddress(str string) (Address, error) {
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
input := str
// Split input into components (prepend with // to assert host by default)
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
str = "//" + str
}
u, err := url.Parse(str)
if err != nil {
return Address{}, err
}
// separate host and port
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
host, port, err = net.SplitHostPort(u.Host + ":")
if err != nil {
host = u.Host
}
}
// see if we can set port based off scheme
if port == "" {
if u.Scheme == "http" {
port = httpPort
} else if u.Scheme == "https" {
port = httpsPort
}
}
// repeated or conflicting scheme is confusing, so error
if u.Scheme != "" && (port == "http" || port == "https") {
return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input)
}
// error if scheme and port combination violate convention
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
}
// standardize http and https ports to their respective port numbers
if port == "http" {
u.Scheme = "http"
port = httpPort
} else if port == "https" {
u.Scheme = "https"
port = httpsPort
}
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
}
const ( const (
defaultPort = "2015" defaultPort = "2015"
caseSensitivePath = false caseSensitivePath = false // TODO: Used?
) )

View File

@ -1,22 +1,11 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile package httpcaddyfile
import "testing" import (
"strings"
"testing"
)
func TestStandardizeAddress(t *testing.T) { func TestParseAddress(t *testing.T) {
for i, test := range []struct { for i, test := range []struct {
input string input string
scheme, host, port, path string scheme, host, port, path string
@ -31,14 +20,15 @@ func TestStandardizeAddress(t *testing.T) {
{`[::1]`, "", "::1", "", "", false}, {`[::1]`, "", "::1", "", "", false},
{`[::1]:1234`, "", "::1", "1234", "", false}, {`[::1]:1234`, "", "::1", "1234", "", false},
{`:`, "", "", "", "", false}, {`:`, "", "", "", "", false},
{`localhost:http`, "http", "localhost", "80", "", false}, {`:http`, "", "", "", "", true},
{`localhost:https`, "https", "localhost", "443", "", false}, {`:https`, "", "", "", "", true},
{`:http`, "http", "", "80", "", false}, {`localhost:http`, "", "", "", "", true}, // using service name in port is verboten, as of Go 1.12.8
{`:https`, "https", "", "443", "", false}, {`localhost:https`, "", "", "", "", true},
{`http://localhost:https`, "", "", "", "", true}, // conflict {`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme {`http://localhost:http`, "", "", "", "", true}, // repeated scheme
{`http://localhost:443`, "", "", "", "", true}, // not conventional {`host:https/path`, "", "", "", "", true},
{`https://localhost:80`, "", "", "", "", true}, // not conventional {`http://localhost:443`, "", "", "", "", true}, // not conventional
{`https://localhost:80`, "", "", "", "", true}, // not conventional
{`http://localhost`, "http", "localhost", "80", "", false}, {`http://localhost`, "http", "localhost", "80", "", false},
{`https://localhost`, "https", "localhost", "443", "", false}, {`https://localhost`, "https", "localhost", "443", "", false},
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false}, {`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
@ -58,10 +48,9 @@ func TestStandardizeAddress(t *testing.T) {
{`http://host/path`, "http", "host", "80", "/path", false}, {`http://host/path`, "http", "host", "80", "/path", false},
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false}, {`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
{`host:80/path`, "", "host", "80", "/path", false}, {`host:80/path`, "", "host", "80", "/path", false},
{`host:https/path`, "https", "host", "443", "/path", false},
{`/path`, "", "", "", "/path", false}, {`/path`, "", "", "", "/path", false},
} { } {
actual, err := standardizeAddress(test.input) actual, err := ParseAddress(test.input)
if err != nil && !test.shouldErr { if err != nil && !test.shouldErr {
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err) t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
@ -88,24 +77,6 @@ func TestStandardizeAddress(t *testing.T) {
} }
} }
func TestAddressVHost(t *testing.T) {
for i, test := range []struct {
addr Address
expected string
}{
{Address{Original: "host:1234"}, "host:1234"},
{Address{Original: "host:1234/foo"}, "host:1234/foo"},
{Address{Original: "host/foo"}, "host/foo"},
{Address{Original: "http://host/foo"}, "host/foo"},
{Address{Original: "https://host/foo"}, "host/foo"},
} {
actual := test.addr.VHost()
if actual != test.expected {
t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
}
}
}
func TestAddressString(t *testing.T) { func TestAddressString(t *testing.T) {
for i, test := range []struct { for i, test := range []struct {
addr Address addr Address
@ -127,3 +98,69 @@ func TestAddressString(t *testing.T) {
} }
} }
} }
func TestKeyNormalization(t *testing.T) {
testCases := []struct {
input string
expect string
}{
{
input: "http://host:1234/path",
expect: "http://host:1234/path",
},
{
input: "HTTP://A/ABCDEF",
expect: "http://a/ABCDEF",
},
{
input: "A/ABCDEF",
expect: "a/ABCDEF",
},
{
input: "A:2015/Path",
expect: "a:2015/Path",
},
{
input: ":80",
expect: ":80",
},
{
input: ":443",
expect: ":443",
},
{
input: ":1234",
expect: ":1234",
},
{
input: "",
expect: "",
},
{
input: ":",
expect: "",
},
{
input: "[::]",
expect: "::",
},
}
for i, tc := range testCases {
addr, err := ParseAddress(tc.input)
if err != nil {
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
continue
}
expect := tc.expect
if !caseSensitivePath {
// every other part of the address should be lowercased when normalized,
// so simply lower-case the whole thing to do case-insensitive comparison
// of the path as well
expect = strings.ToLower(expect)
}
if actual := addr.Normalize().Key(); actual != expect {
t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, expect)
}
}
}

View File

@ -19,93 +19,221 @@ import (
"fmt" "fmt"
"html" "html"
"net/http" "net/http"
"reflect"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
func (st *ServerType) parseRoot( func init() {
tkns []caddyfile.Token, RegisterDirective("bind", parseBind)
matcherDefs map[string]map[string]json.RawMessage, RegisterDirective("root", parseRoot)
warnings *[]caddyconfig.Warning, RegisterDirective("tls", parseTLS)
) ([]caddyhttp.Route, error) { RegisterHandlerDirective("redir", parseRedir)
var routes []caddyhttp.Route
matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
if err != nil {
return nil, err
}
for _, mst := range matchersAndTokens {
d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
var root string
for d.Next() {
if !d.NextArg() {
return nil, d.ArgErr()
}
root = d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
}
varsHandler := caddyhttp.VarsMiddleware{"root": root}
route := caddyhttp.Route{
Handle: []json.RawMessage{
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", warnings),
},
}
if mst.matcherSet != nil {
route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
}
routes = append(routes, route)
}
return routes, nil
} }
func (st *ServerType) parseRedir( func parseBind(h Helper) ([]ConfigValue, error) {
tkns []caddyfile.Token, var lnHosts []string
matcherDefs map[string]map[string]json.RawMessage, for h.Next() {
warnings *[]caddyconfig.Warning, lnHosts = append(lnHosts, h.RemainingArgs()...)
) ([]caddyhttp.Route, error) { }
var routes []caddyhttp.Route return h.NewBindAddresses(lnHosts), nil
}
matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings) func parseRoot(h Helper) ([]ConfigValue, error) {
if !h.Next() {
return nil, h.ArgErr()
}
matcherSet, ok, err := h.MatcherToken()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !ok {
// no matcher token; oops
h.Dispenser.Prev()
}
for _, mst := range matchersAndTokens { if !h.NextArg() {
var route caddyhttp.Route return nil, h.ArgErr()
}
root := h.Val()
if h.NextArg() {
return nil, h.ArgErr()
}
d := caddyfile.NewDispenser("Caddyfile", mst.tokens) varsHandler := caddyhttp.VarsMiddleware{"root": root}
route := caddyhttp.Route{
HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", nil),
},
}
if matcherSet != nil {
route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
}
for d.Next() { return h.NewVarsRoute(route), nil
if !d.NextArg() { }
return nil, d.ArgErr()
}
to := d.Val()
var code string func parseTLS(h Helper) ([]ConfigValue, error) {
if d.NextArg() { var configVals []ConfigValue
code = d.Val()
cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader
var folderLoader caddytls.FolderLoader
var mgr caddytls.ACMEManagerMaker
var off bool
for h.Next() {
// file certificate loader
firstLine := h.RemainingArgs()
switch len(firstLine) {
case 0:
case 1:
if firstLine[0] == "off" {
off = true
} else {
mgr.Email = firstLine[0]
} }
if code == "permanent" { case 2:
code = "301" fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
Certificate: firstLine[0],
Key: firstLine[1],
// TODO: add tags, for enterprise module's certificate selection
})
default:
return nil, h.ArgErr()
}
var hasBlock bool
for h.NextBlock() {
hasBlock = true
switch h.Val() {
// connection policy
case "protocols":
args := h.RemainingArgs()
if len(args) == 0 {
return nil, h.SyntaxErr("one or two protocols")
}
if len(args) > 0 {
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
}
cp.ProtocolMin = args[0]
}
if len(args) > 1 {
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
}
cp.ProtocolMax = args[1]
}
case "ciphers":
for h.NextArg() {
if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
}
cp.CipherSuites = append(cp.CipherSuites, h.Val())
}
case "curves":
for h.NextArg() {
if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
}
cp.Curves = append(cp.Curves, h.Val())
}
case "alpn":
args := h.RemainingArgs()
if len(args) == 0 {
return nil, h.ArgErr()
}
cp.ALPN = args
// certificate folder loader
case "load":
folderLoader = append(folderLoader, h.RemainingArgs()...)
// automation policy
case "ca":
arg := h.RemainingArgs()
if len(arg) != 1 {
return nil, h.ArgErr()
}
mgr.CA = arg[0]
// TODO: other properties for automation manager
} }
if code == "temporary" || code == "" { }
code = "307"
} // a naked tls directive is not allowed
var body string if len(firstLine) == 0 && !hasBlock {
if code == "meta" { return nil, h.ArgErr()
// Script tag comes first since that will better imitate a redirect in the browser's }
// history, but the meta tag is a fallback for most non-JS clients. }
const metaRedir = `<!DOCTYPE html>
// connection policy
configVals = append(configVals, ConfigValue{
Class: "tls.connection_policy",
Value: cp,
})
// certificate loaders
if len(fileLoader) > 0 {
configVals = append(configVals, ConfigValue{
Class: "tls.certificate_loader",
Value: fileLoader,
})
}
if len(folderLoader) > 0 {
configVals = append(configVals, ConfigValue{
Class: "tls.certificate_loader",
Value: folderLoader,
})
}
// automation policy
if off {
configVals = append(configVals, ConfigValue{
Class: "tls.off",
Value: true,
})
} else if !reflect.DeepEqual(mgr, caddytls.ACMEManagerMaker{}) {
configVals = append(configVals, ConfigValue{
Class: "tls.automation_manager",
Value: mgr,
})
}
return configVals, nil
}
func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
if !h.Next() {
return nil, h.ArgErr()
}
if !h.NextArg() {
return nil, h.ArgErr()
}
to := h.Val()
var code string
if h.NextArg() {
code = h.Val()
}
if code == "permanent" {
code = "301"
}
if code == "temporary" || code == "" {
code = "307"
}
var body string
if code == "meta" {
// Script tag comes first since that will better imitate a redirect in the browser's
// history, but the meta tag is a fallback for most non-JS clients.
const metaRedir = `<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Redirecting...</title> <title>Redirecting...</title>
@ -115,143 +243,13 @@ func (st *ServerType) parseRedir(
<body>Redirecting to <a href="%s">%s</a>...</body> <body>Redirecting to <a href="%s">%s</a>...</body>
</html> </html>
` `
safeTo := html.EscapeString(to) safeTo := html.EscapeString(to)
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo) body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
}
handler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString(code),
Headers: http.Header{"Location": []string{to}},
Body: body,
}
route.Handle = append(route.Handle,
caddyconfig.JSONModuleObject(handler, "handler", "static_response", warnings))
}
if mst.matcherSet != nil {
route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
}
routes = append(routes, route)
} }
return routes, nil return caddyhttp.StaticResponse{
} StatusCode: caddyhttp.WeakString(code),
Headers: http.Header{"Location": []string{to}},
func (st *ServerType) parseTLSAutomationManager(d *caddyfile.Dispenser) (caddytls.ACMEManagerMaker, error) { Body: body,
var m caddytls.ACMEManagerMaker }, nil
for d.Next() {
firstLine := d.RemainingArgs()
if len(firstLine) == 1 && firstLine[0] != "off" {
m.Email = firstLine[0]
}
var hasBlock bool
for d.NextBlock() {
hasBlock = true
switch d.Val() {
case "ca":
arg := d.RemainingArgs()
if len(arg) != 1 {
return m, d.ArgErr()
}
m.CA = arg[0]
// TODO: other properties
}
}
// a naked tls directive is not allowed
if len(firstLine) == 0 && !hasBlock {
return m, d.ArgErr()
}
}
return m, nil
}
func (st *ServerType) parseTLSCerts(d *caddyfile.Dispenser) (map[string]caddytls.CertificateLoader, error) {
var fileLoader caddytls.FileLoader
var folderLoader caddytls.FolderLoader
for d.Next() {
// file loader
firstLine := d.RemainingArgs()
if len(firstLine) == 2 {
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
Certificate: firstLine[0],
Key: firstLine[1],
// TODO: tags, for enterprise module's certificate selection
})
}
// folder loader
for d.NextBlock() {
if d.Val() == "load" {
folderLoader = append(folderLoader, d.RemainingArgs()...)
}
}
}
// put configured loaders into the map
loaders := make(map[string]caddytls.CertificateLoader)
if len(fileLoader) > 0 {
loaders["load_files"] = fileLoader
}
if len(folderLoader) > 0 {
loaders["load_folders"] = folderLoader
}
return loaders, nil
}
func (st *ServerType) parseTLSConnPolicy(d *caddyfile.Dispenser) (*caddytls.ConnectionPolicy, error) {
cp := new(caddytls.ConnectionPolicy)
for d.Next() {
for d.NextBlock() {
switch d.Val() {
case "protocols":
args := d.RemainingArgs()
if len(args) == 0 {
return nil, d.SyntaxErr("one or two protocols")
}
if len(args) > 0 {
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
}
cp.ProtocolMin = args[0]
}
if len(args) > 1 {
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
}
cp.ProtocolMax = args[1]
}
case "ciphers":
for d.NextArg() {
if _, ok := caddytls.SupportedCipherSuites[d.Val()]; !ok {
return nil, d.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", d.Val())
}
cp.CipherSuites = append(cp.CipherSuites, d.Val())
}
case "curves":
for d.NextArg() {
if _, ok := caddytls.SupportedCurves[d.Val()]; !ok {
return nil, d.Errf("Wrong curve name or curve not supported: '%s'", d.Val())
}
cp.Curves = append(cp.Curves, d.Val())
}
case "alpn":
args := d.RemainingArgs()
if len(args) == 0 {
return nil, d.ArgErr()
}
cp.ALPN = args
}
}
}
return cp, nil
} }

View File

@ -0,0 +1,182 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"encoding/json"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// defaultDirectiveOrder specifies the order
// to apply directives in HTTP routes.
// TODO: finish the ability to customize this
var defaultDirectiveOrder = []string{
"rewrite",
"try_files",
"headers",
"encode",
"templates",
"redir",
"static_response", // TODO: "reply" or "respond"?
"reverse_proxy",
"file_server",
}
// RegisterDirective registers a unique directive dir with an
// associated unmarshaling (setup) function. When directive dir
// is encountered in a Caddyfile, setupFunc will be called to
// unmarshal its tokens.
func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
if _, ok := registeredDirectives[dir]; ok {
panic("directive " + dir + " already registered")
}
registeredDirectives[dir] = setupFunc
}
// RegisterHandlerDirective is like RegisterDirective, but for
// directives which specifically output only an HTTP handler.
func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
RegisterDirective(dir, func(h Helper) ([]ConfigValue, error) {
if !h.Next() {
return nil, h.ArgErr()
}
matcherSet, ok, err := h.MatcherToken()
if err != nil {
return nil, err
}
if ok {
h.Dispenser.Delete() // strip matcher token
}
h.Dispenser.Reset() // pretend this lookahead never happened
val, err := setupFunc(h)
if err != nil {
return nil, err
}
return h.NewRoute(matcherSet, val), nil
})
}
// Helper is a type which helps setup a value from
// Caddyfile tokens.
type Helper struct {
*caddyfile.Dispenser
warnings *[]caddyconfig.Warning
matcherDefs map[string]map[string]json.RawMessage
}
// JSON converts val into JSON. Any errors are added to warnings.
func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawMessage {
return caddyconfig.JSON(val, h.warnings)
}
// MatcherToken assumes the current token is (possibly) a matcher, and
// if so, returns the matcher set along with a true value. If the current
// token is not a matcher, nil and false is returned. Note that a true
// value may be returned with a nil matcher set if it is a catch-all.
func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
if !h.NextArg() {
return nil, false, nil
}
return matcherSetFromMatcherToken(h.Dispenser.Token(), h.matcherDefs, h.warnings)
}
// NewRoute returns config values relevant to creating a new HTTP route.
func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
handler caddyhttp.MiddlewareHandler) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleName(handler))
if err != nil {
// TODO: append to warnings
}
var matcherSetsRaw []map[string]json.RawMessage
if matcherSet != nil {
matcherSetsRaw = append(matcherSetsRaw, matcherSet)
}
return []ConfigValue{
{
Class: "route",
Value: caddyhttp.Route{
MatcherSetsRaw: matcherSetsRaw,
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
},
},
}
}
// NewBindAddresses returns config values relevant to adding
// listener bind addresses to the config.
func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
return []ConfigValue{{Class: "bind", Value: addrs}}
}
// NewVarsRoute returns config values relevant to adding a
// "vars" wrapper route to the config.
func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue {
return []ConfigValue{{Class: "var", Value: route}}
}
// ConfigValue represents a value to be added to the final
// configuration, or a value to be consulted when building
// the final configuration.
type ConfigValue struct {
// The kind of value this is. As the config is
// being built, the adapter will look in the
// "pile" for values belonging to a certain
// class when it is setting up a certain part
// of the config. The associated value will be
// type-asserted and placed accordingly.
Class string
// The value to be used when building the config.
// Generally its type is associated with the
// name of the Class.
Value interface{}
directive string
}
// serverBlock pairs a Caddyfile server block
// with a "pile" of config values, keyed by class
// name.
type serverBlock struct {
block caddyfile.ServerBlock
pile map[string][]ConfigValue // config values obtained from directives
}
type (
// UnmarshalFunc is a function which can unmarshal Caddyfile
// tokens into zero or more config values using a Helper type.
// These are passed in a call to RegisterDirective.
UnmarshalFunc func(h Helper) ([]ConfigValue, error)
// UnmarshalHandlerFunc is like UnmarshalFunc, except the
// output of the unmarshaling is an HTTP handler. This
// function does not need to deal with HTTP request matching
// which is abstracted away. Since writing HTTP handlers
// with Caddyfile support is very common, this is a more
// convenient way to add a handler to the chain since a lot
// of the details common to HTTP handlers are taken care of
// for you. These are passed to a call to
// RegisterHandlerDirective.
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
)
var registeredDirectives = make(map[string]UnmarshalFunc)

View File

@ -17,7 +17,6 @@ package httpcaddyfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@ -55,38 +54,3 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin
} }
return matchers, nil return matchers, nil
} }
// directiveBuckets returns a list of middleware/handler directives.
// Buckets are ordered, and directives should be evaluated in their
// bucket order. Within a bucket, directives are not ordered. Hence,
// the return value has a slice of buckets, where each bucket is a
// map, which is a strongly-typed reminder that directives within a
// bucket are not ordered.
func directiveBuckets() []map[string]struct{} {
directiveBuckets := []map[string]struct{}{
// prefer odd-numbered buckets; evens are there for contingencies
{}, // 0
{}, // 1 - keep empty unless necessary
{}, // 2
{}, // 3 - first handlers, last responders
{}, // 4
{}, // 5 - middle of chain
{}, // 6
{}, // 7 - last handlers, first responders
{}, // 8
{}, // 9 - keep empty unless necessary
{}, // 10
}
for _, mod := range caddy.GetModules("http.handlers") {
if hd, ok := mod.New().(HandlerDirective); ok {
bucket := hd.Bucket()
if bucket < 0 || bucket >= len(directiveBuckets) {
log.Printf("[ERROR] directive %s: bucket out of range [0-%d): %d; skipping",
mod.Name, len(directiveBuckets), bucket)
continue
}
directiveBuckets[bucket][mod.ID()] = struct{}{}
}
}
return directiveBuckets
}

View File

@ -17,17 +17,18 @@ package httpcaddyfile
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"reflect" "reflect"
"sort"
"strconv" "strconv"
"strings" "strings"
"github.com/mholt/certmagic"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/mholt/certmagic"
) )
func init() { func init() {
@ -38,24 +39,57 @@ func init() {
type ServerType struct { type ServerType struct {
} }
// ValidDirectives returns the list of known directives. // TODO: error on unrecognized directives
func (ServerType) ValidDirectives() []string {
dirs := []string{"matcher", "root", "tls", "redir"} // TODO: put special-case (hard-coded, or non-handler) directives here
for _, mod := range caddy.GetModules("http.handlers") {
if _, ok := mod.New().(HandlerDirective); ok {
dirs = append(dirs, mod.ID())
}
}
return dirs
}
// Setup makes a config from the tokens. // Setup makes a config from the tokens.
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) { options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
var warnings []caddyconfig.Warning var warnings []caddyconfig.Warning
var serverBlocks []serverBlock
for _, sblock := range originalServerBlocks {
serverBlocks = append(serverBlocks, serverBlock{
block: sblock,
pile: make(map[string][]ConfigValue),
})
}
for _, sb := range serverBlocks {
// extract matcher definitions
d := sb.block.DispenseDirective("matcher")
matcherDefs, err := st.parseMatcherDefinitions(d)
if err != nil {
return nil, nil, err
}
for _, segment := range sb.block.Segments {
dir := segment.Directive()
if dir == "matcher" {
// TODO: This is a special case because we pre-processed it; handle this better
continue
}
if dirFunc, ok := registeredDirectives[dir]; ok {
results, err := dirFunc(Helper{
Dispenser: segment.NewDispenser(),
warnings: &warnings,
matcherDefs: matcherDefs,
})
if err != nil {
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
}
for _, result := range results {
result.directive = dir
sb.pile[result.Class] = append(sb.pile[result.Class], result)
}
} else {
// TODO: this should be an error
log.Printf("%s not registered", dir)
}
}
}
// map // map
sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks) sbmap, err := st.mapAddressToServerBlocks(serverBlocks)
if err != nil { if err != nil {
return nil, warnings, err return nil, warnings, err
} }
@ -63,6 +97,22 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
// reduce // reduce
pairings := st.consolidateAddrMappings(sbmap) pairings := st.consolidateAddrMappings(sbmap)
// TODO: shorthand placeholders
// for _, p := range pairings {
// for _, sblock := range p.serverBlocks {
// for _, tokens := range sblock.Tokens {
// for i := 0; i < len(tokens); i++ {
// switch tokens[i].Text {
// case "{uri}":
// tokens[i].Text = "{http.request.uri}"
// case "{path}":
// tokens[i].Text = "{http.request.uri.path}"
// }
// }
// }
// }
// }
// each pairing of listener addresses to list of server // each pairing of listener addresses to list of server
// blocks is basically a server definition // blocks is basically a server definition
servers, err := st.serversFromPairings(pairings, &warnings) servers, err := st.serversFromPairings(pairings, &warnings)
@ -81,45 +131,33 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)} tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
for _, p := range pairings { for _, p := range pairings {
for _, sblock := range p.serverBlocks { for _, sblock := range p.serverBlocks {
if tkns, ok := sblock.Tokens["tls"]; ok { // tls automation policies
// extract all unique hostnames from the server block if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
// keys, then convert to a slice for use in the TLS app for _, mmVal := range mmVals {
hostMap := make(map[string]struct{}) mm := mmVal.Value.(caddytls.ManagerMaker)
for _, sblockKey := range sblock.Keys { sblockHosts, err := st.autoHTTPSHosts(sblock)
addr, err := standardizeAddress(sblockKey)
if err != nil { if err != nil {
return nil, warnings, fmt.Errorf("parsing server block key: %v", err) return nil, warnings, err
} }
hostMap[addr.Host] = struct{}{} tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
} Hosts: sblockHosts,
sblockHosts := make([]string, 0, len(hostMap)) ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
for host := range hostMap { })
sblockHosts = append(sblockHosts, host)
} }
}
// parse tokens to get ACME manager config // tls certificate loaders
acmeMgr, err := st.parseTLSAutomationManager(caddyfile.NewDispenser("Caddyfile", tkns)) if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
if err != nil { for _, clVal := range clVals {
return nil, warnings, err loader := clVal.Value.(caddytls.CertificateLoader)
} loaderName := caddy.GetModuleName(loader)
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
Hosts: sblockHosts,
ManagementRaw: caddyconfig.JSONModuleObject(acmeMgr, "module", "acme", &warnings),
})
// parse tokens to get certificates to be loaded manually
certLoaders, err := st.parseTLSCerts(caddyfile.NewDispenser("Caddyfile", tkns))
if err != nil {
return nil, nil, err
}
for loaderName, loader := range certLoaders {
tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings) tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
} }
} }
} }
} }
// consolidate automation policies that are the exact same
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
// annnd the top-level config, then we're done! // annnd the top-level config, then we're done!
cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)} cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
@ -140,10 +178,11 @@ func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]stri
// first get each unique hostname // first get each unique hostname
hostMap := make(map[string]struct{}) hostMap := make(map[string]struct{})
for _, sblockKey := range sb.Keys { for _, sblockKey := range sb.Keys {
addr, err := standardizeAddress(sblockKey) addr, err := ParseAddress(sblockKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing server block key: %v", err) return nil, fmt.Errorf("parsing server block key: %v", err)
} }
addr = addr.Normalize()
hostMap[addr.Host] = struct{}{} hostMap[addr.Host] = struct{}{}
} }
@ -167,121 +206,75 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
} }
for _, sblock := range p.serverBlocks { for _, sblock := range p.serverBlocks {
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock) matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
if err != nil { if err != nil {
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Keys, err) return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
} }
// extract matcher definitions // if there are user-defined variables, then siteVarSubroute will
d := caddyfile.NewDispenser("Caddyfile", sblock.Tokens["matcher"]) // wrap the handlerSubroute; otherwise handlerSubroute will be the
matcherDefs, err := st.parseMatcherDefinitions(d) // site's primary subroute.
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
// tls: connection policies and toggle auto HTTPS
autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, ok := sblock.pile["tls.off"]; ok {
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute) // tls off: disable TLS (and automatic HTTPS) for server block's names
if srv.AutoHTTPS == nil {
// built-in directives srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
// root: path to root of site
if tkns, ok := sblock.Tokens["root"]; ok {
routes, err := st.parseRoot(tkns, matcherDefs, warnings)
if err != nil {
return nil, err
} }
siteVarSubroute.Routes = append(siteVarSubroute.Routes, routes...) srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
} } else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
// tls connection policies
// tls: off and conn policies for _, cpVal := range cpVals {
if tkns, ok := sblock.Tokens["tls"]; ok { cp := cpVal.Value.(*caddytls.ConnectionPolicy)
// get the hosts for this server block... // only create if there is a non-empty policy
hosts, err := st.hostsFromServerBlockKeys(sblock) if !reflect.DeepEqual(cp, new(caddytls.ConnectionPolicy)) {
if err != nil { // make sure the policy covers all hostnames from the block
return nil, err hosts, err := st.hostsFromServerBlockKeys(sblock.block)
}
// ...and of those, which ones qualify for auto HTTPS
var autoHTTPSQualifiedHosts []string
for _, h := range hosts {
if certmagic.HostQualifies(h) {
autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
}
}
if len(tkns) == 2 && tkns[1].Text == "off" {
// tls off: disable TLS (and automatic HTTPS) for server block's names
if srv.AutoHTTPS == nil {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
} else {
// tls connection policies
cp, err := st.parseTLSConnPolicy(caddyfile.NewDispenser("Caddyfile", tkns))
if err != nil {
return nil, err
}
// TODO: are matchers needed if every hostname of the config is matched?
cp.Matchers = map[string]json.RawMessage{
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
}
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
}
}
// set up each handler directive
for _, dirBucket := range directiveBuckets() {
for dir := range dirBucket {
// keep in mind that multiple occurrences of the directive may appear here
tkns, ok := sblock.Tokens[dir]
if !ok {
continue
}
// extract matcher sets from matcher tokens, if any
matcherSetsMap, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
mod, err := caddy.GetModule("http.handlers." + dir)
if err != nil {
return nil, fmt.Errorf("getting handler module '%s': %v", mod.Name, err)
}
// the tokens have been divided by matcher set for us,
// so iterate each one and set them up
for _, mst := range matcherSetsMap {
unm, ok := mod.New().(caddyfile.Unmarshaler)
if !ok {
return nil, fmt.Errorf("handler module '%s' is not a Caddyfile unmarshaler", mod.Name)
}
err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(d.File(), mst.tokens))
if err != nil { if err != nil {
return nil, err return nil, err
} }
handler, ok := unm.(caddyhttp.MiddlewareHandler)
if !ok {
return nil, fmt.Errorf("handler module '%s' does not implement caddyhttp.MiddlewareHandler interface", mod.Name)
}
route := caddyhttp.Route{ // TODO: are matchers needed if every hostname of the config is matched?
Handle: []json.RawMessage{ cp.Matchers = map[string]json.RawMessage{
caddyconfig.JSONModuleObject(handler, "handler", dir, warnings), "sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
},
} }
if mst.matcherSet != nil { srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
}
handlerSubroute.Routes = append(handlerSubroute.Routes, route)
} }
} }
// TODO: consolidate equal conn policies
} }
// redir: static responses that redirect // vars: special routes that will have to wrap the normal handlers
if tkns, ok := sblock.Tokens["redir"]; ok { // so that these variables can be used across their matchers too
routes, err := st.parseRedir(tkns, matcherDefs, warnings) for _, cfgVal := range sblock.pile["var"] {
if err != nil { siteVarSubroute.Routes = append(siteVarSubroute.Routes, cfgVal.Value.(caddyhttp.Route))
return nil, err }
// set up each handler directive
dirRoutes := sblock.pile["route"]
// TODO: The ordering here depends on... if there is a list of
// directives to use, then sort by that, otherwise just use in
// the order they appear in the slice (which is the order they
// appeared in the Caddyfile)
sortByList := true
if sortByList {
dirPositions := make(map[string]int)
for i, dir := range defaultDirectiveOrder {
dirPositions[dir] = i
} }
handlerSubroute.Routes = append(handlerSubroute.Routes, routes...) sort.SliceStable(dirRoutes, func(i, j int) bool {
iDir, jDir := dirRoutes[i].directive, dirRoutes[j].directive
return dirPositions[iDir] < dirPositions[jDir]
})
}
for _, r := range dirRoutes {
handlerSubroute.Routes = append(handlerSubroute.Routes, r.Value.(caddyhttp.Route))
} }
// the route that contains the site's handlers will // the route that contains the site's handlers will
@ -298,7 +291,7 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
siteSubroute.Routes = append( siteSubroute.Routes = append(
siteVarSubroute.Routes, siteVarSubroute.Routes,
caddyhttp.Route{ caddyhttp.Route{
Handle: []json.RawMessage{ HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings), caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
}, },
}, },
@ -308,8 +301,8 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes) siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
srv.Routes = append(srv.Routes, caddyhttp.Route{ srv.Routes = append(srv.Routes, caddyhttp.Route{
MatcherSets: matcherSetsEnc, MatcherSetsRaw: matcherSetsEnc,
Handle: []json.RawMessage{ HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings), caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
}, },
}) })
@ -323,16 +316,32 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
return servers, nil return servers, nil
} }
func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
// get the hosts for this server block...
hosts, err := st.hostsFromServerBlockKeys(sb.block)
if err != nil {
return nil, err
}
// ...and of those, which ones qualify for auto HTTPS
var autoHTTPSQualifiedHosts []string
for _, h := range hosts {
if certmagic.HostQualifies(h) {
autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
}
}
return autoHTTPSQualifiedHosts, nil
}
// consolidateRoutes combines routes with the same properties // consolidateRoutes combines routes with the same properties
// (same matchers, same Terminal and Group settings) for a // (same matchers, same Terminal and Group settings) for a
// cleaner overall output. // cleaner overall output.
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList { func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
for i := 0; i < len(routes)-1; i++ { for i := 0; i < len(routes)-1; i++ {
if reflect.DeepEqual(routes[i].MatcherSets, routes[i+1].MatcherSets) && if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) &&
routes[i].Terminal == routes[i+1].Terminal && routes[i].Terminal == routes[i+1].Terminal &&
routes[i].Group == routes[i+1].Group { routes[i].Group == routes[i+1].Group {
// keep the handlers in the same order, then splice out repetitive route // keep the handlers in the same order, then splice out repetitive route
routes[i].Handle = append(routes[i].Handle, routes[i+1].Handle...) routes[i].HandlersRaw = append(routes[i].HandlersRaw, routes[i+1].HandlersRaw...)
routes = append(routes[:i+1], routes[i+2:]...) routes = append(routes[:i+1], routes[i+2:]...)
i-- i--
} }
@ -340,53 +349,26 @@ func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
return routes return routes
} }
func (st *ServerType) tokensToMatcherSets( // consolidateAutomationPolicies combines automation policies that are the same,
tkns []caddyfile.Token, // for a cleaner overall output.
matcherDefs map[string]map[string]json.RawMessage, func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.AutomationPolicy {
warnings *[]caddyconfig.Warning, for i := 0; i < len(aps); i++ {
) (map[string]matcherSetAndTokens, error) { for j := 0; j < len(aps); j++ {
m := make(map[string]matcherSetAndTokens) if j == i {
continue
for len(tkns) > 0 {
d := caddyfile.NewDispenser("Caddyfile", tkns)
d.Next() // consume directive token
// look for matcher; it should be the next argument
var matcherToken caddyfile.Token
var matcherSet map[string]json.RawMessage
if d.NextArg() {
var ok bool
var err error
matcherSet, ok, err = st.matcherSetFromMatcherToken(d.Token(), matcherDefs, warnings)
if err != nil {
return nil, err
} }
if ok { if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) {
// found a matcher; save it, then splice it out aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...)
// since we don't want to parse it again
matcherToken = d.Token()
tkns = d.Delete()
} }
d.RemainingArgs() // advance to end of line aps = append(aps[:j], aps[j+1:]...)
i--
break
} }
for d.NextBlock() {
// skip entire block including any nested blocks; all
// we care about is accessing next directive occurrence
for d.Nested() {
d.NextBlock()
}
}
end := d.Cursor() + 1
m[matcherToken.Text] = matcherSetAndTokens{
matcherSet: matcherSet,
tokens: append(m[matcherToken.Text].tokens, tkns[:end]...),
}
tkns = tkns[end:]
} }
return m, nil return aps
} }
func (st *ServerType) matcherSetFromMatcherToken( func matcherSetFromMatcherToken(
tkn caddyfile.Token, tkn caddyfile.Token,
matcherDefs map[string]map[string]json.RawMessage, matcherDefs map[string]map[string]json.RawMessage,
warnings *[]caddyconfig.Warning, warnings *[]caddyconfig.Warning,
@ -424,10 +406,11 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
var matcherPairs []*hostPathPair var matcherPairs []*hostPathPair
for _, key := range sblock.Keys { for _, key := range sblock.Keys {
addr, err := standardizeAddress(key) addr, err := ParseAddress(key)
if err != nil { if err != nil {
return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err) return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
} }
addr = addr.Normalize()
// choose a matcher pair that should be shared by this // choose a matcher pair that should be shared by this
// server block; if none exists yet, create one // server block; if none exists yet, create one
@ -504,14 +487,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]
return msEncoded, nil return msEncoded, nil
} }
// HandlerDirective implements a directive for an HTTP handler,
// in that it can unmarshal its own configuration from Caddyfile
// tokens and also specify which directive bucket it belongs in.
type HandlerDirective interface {
caddyfile.Unmarshaler
Bucket() int
}
// tryInt tries to convert str to an integer. If it fails, it downgrades // tryInt tries to convert str to an integer. If it fails, it downgrades
// the error to a warning and returns 0. // the error to a warning and returns 0.
func tryInt(str string, warnings *[]caddyconfig.Warning) int { func tryInt(str string, warnings *[]caddyconfig.Warning) int {
@ -535,7 +510,7 @@ type matcherSetAndTokens struct {
// served on those addresses. // served on those addresses.
type sbAddrAssociation struct { type sbAddrAssociation struct {
addresses []string addresses []string
serverBlocks []caddyfile.ServerBlock serverBlocks []serverBlock
} }
// Interface guard // Interface guard

View File

@ -99,11 +99,16 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{},
return nil, fmt.Errorf("module '%s' has no constructor", mod.Name) return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
} }
val := mod.New() val := mod.New().(interface{})
// value must be a pointer for unmarshaling into concrete type // value must be a pointer for unmarshaling into concrete type, even if
// the module's concrete type is a slice or map; New() *should* return
// a pointer, otherwise unmarshaling errors or panics will occur
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr { if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
val = reflect.New(rv.Type()).Elem().Addr().Interface() log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
" so we are using reflection to make a pointer instead; please fix this by"+
" using new(Type) or &Type notation in your module's New() function.", name)
val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
} }
// fill in its config only if there is a config to fill in // fill in its config only if there is a config to fill in

View File

@ -23,48 +23,76 @@ import (
"sync" "sync"
) )
// Module represents a Caddy module. // Module is a type that is used as a Caddy module.
type Module struct { type Module interface {
// This method indicates the type is a Caddy
// module. The returned ModuleInfo must have
// both a name and a constructor function.
// This method must not have any side-effects.
CaddyModule() ModuleInfo
}
// ModuleInfo represents a registered Caddy module.
type ModuleInfo struct {
// Name is the full name of the module. It // Name is the full name of the module. It
// must be unique and properly namespaced. // must be unique and properly namespaced.
Name string Name string
// New returns a new, empty instance of // New returns a pointer to a new, empty
// the module's type. The host module // instance of the module's type. The host
// which loads this module will likely // module which instantiates this module will
// invoke methods on the returned value. // likely type-assert and invoke methods on
// It must return a pointer; if not, it // the returned value. This function must not
// is converted into one. // have any side-effects.
New func() interface{} New func() Module
}
// ID returns a module's ID, which is the
// last element of its name.
func (m Module) ID() string {
if m.Name == "" {
return ""
}
parts := strings.Split(m.Name, ".")
return parts[len(parts)-1]
} }
// Namespace returns the module's namespace (scope) // Namespace returns the module's namespace (scope)
// which is all but the last element of its name. // which is all but the last element of its name.
func (m Module) Namespace() string { // If there is no explicit namespace in the name,
lastDot := strings.LastIndex(m.Name, ".") // the whole name is considered the namespace.
func (mi ModuleInfo) Namespace() string {
lastDot := strings.LastIndex(mi.Name, ".")
if lastDot < 0 { if lastDot < 0 {
return "" return mi.Name
} }
return m.Name[:lastDot] return mi.Name[:lastDot]
} }
func (m Module) String() string { return m.Name } // ID returns a module's ID, which is the
// last element of its name.
func (mi ModuleInfo) ID() string {
if mi.Name == "" {
return ""
}
parts := strings.Split(mi.Name, ".")
return parts[len(parts)-1]
}
// RegisterModule registers a module. Modules must call func (mi ModuleInfo) String() string { return mi.Name }
// this function in the init phase of runtime.
func RegisterModule(mod Module) error { // RegisterModule registers a module by receiving a
if mod.Name == "caddy" { // plain/empty value of the module. For registration to
return fmt.Errorf("modules cannot be named 'caddy'") // be properly recorded, this should be called in the
// init phase of runtime. Typically, the module package
// will do this as a side-effect of being imported.
// This function returns an error if the module's info
// is incomplete or invalid, or if the module is
// already registered.
func RegisterModule(instance Module) error {
mod := instance.CaddyModule()
if mod.Name == "" {
return fmt.Errorf("missing ModuleInfo.Name")
}
if mod.Name == "caddy" || mod.Name == "admin" {
return fmt.Errorf("module name '%s' is reserved", mod.Name)
}
if mod.New == nil {
return fmt.Errorf("missing ModuleInfo.New")
}
if val := mod.New(); val == nil {
return fmt.Errorf("ModuleInfo.New must return a non-nil module instance")
} }
modulesMu.Lock() modulesMu.Lock()
@ -77,18 +105,27 @@ func RegisterModule(mod Module) error {
return nil return nil
} }
// GetModule returns a module by its full name. // GetModule returns module information from its full name.
func GetModule(name string) (Module, error) { func GetModule(name string) (ModuleInfo, error) {
modulesMu.Lock() modulesMu.Lock()
defer modulesMu.Unlock() defer modulesMu.Unlock()
m, ok := modules[name] m, ok := modules[name]
if !ok { if !ok {
return Module{}, fmt.Errorf("module not registered: %s", name) return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
} }
return m, nil return m, nil
} }
// GetModuleName returns a module's name from an instance of its value.
// If the value is not a module, an empty name will be returned.
func GetModuleName(instance interface{}) string {
var name string
if mod, ok := instance.(Module); ok {
name = mod.CaddyModule().Name
}
return name
}
// GetModules returns all modules in the given scope/namespace. // GetModules returns all modules in the given scope/namespace.
// For example, a scope of "foo" returns modules named "foo.bar", // For example, a scope of "foo" returns modules named "foo.bar",
// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope // "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope
@ -98,7 +135,7 @@ func GetModule(name string) (Module, error) {
// //
// Because modules are registered to a map, the returned slice // Because modules are registered to a map, the returned slice
// will be sorted to keep it deterministic. // will be sorted to keep it deterministic.
func GetModules(scope string) []Module { func GetModules(scope string) []ModuleInfo {
modulesMu.Lock() modulesMu.Lock()
defer modulesMu.Unlock() defer modulesMu.Unlock()
@ -110,7 +147,7 @@ func GetModules(scope string) []Module {
scopeParts = []string{} scopeParts = []string{}
} }
var mods []Module var mods []ModuleInfo
iterateModules: iterateModules:
for name, m := range modules { for name, m := range modules {
modParts := strings.Split(name, ".") modParts := strings.Split(name, ".")
@ -223,6 +260,6 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
} }
var ( var (
modules = make(map[string]Module) modules = make(map[string]ModuleInfo)
modulesMu sync.Mutex modulesMu sync.Mutex
) )

View File

@ -37,10 +37,7 @@ import (
func init() { func init() {
weakrand.Seed(time.Now().UnixNano()) weakrand.Seed(time.Now().UnixNano())
err := caddy.RegisterModule(caddy.Module{ err := caddy.RegisterModule(App{})
Name: "http",
New: func() interface{} { return new(App) },
})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -58,6 +55,14 @@ type App struct {
ctx caddy.Context ctx caddy.Context
} }
// CaddyModule returns the Caddy module information.
func (App) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http",
New: func() caddy.Module { return new(App) },
}
}
// Provision sets up the app. // Provision sets up the app.
func (app *App) Provision(ctx caddy.Context) error { func (app *App) Provision(ctx caddy.Context) error {
app.ctx = ctx app.ctx = ctx
@ -227,7 +232,7 @@ func (app *App) automaticHTTPS() error {
// find all qualifying domain names, de-duplicated // find all qualifying domain names, de-duplicated
domainSet := make(map[string]struct{}) domainSet := make(map[string]struct{})
for _, route := range srv.Routes { for _, route := range srv.Routes {
for _, matcherSet := range route.matcherSets { for _, matcherSet := range route.MatcherSets {
for _, m := range matcherSet { for _, m := range matcherSet {
if hm, ok := m.(*MatchHost); ok { if hm, ok := m.(*MatchHost); ok {
for _, d := range *hm { for _, d := range *hm {
@ -331,13 +336,13 @@ func (app *App) automaticHTTPS() error {
redirTo += "{http.request.uri}" redirTo += "{http.request.uri}"
redirRoutes = append(redirRoutes, Route{ redirRoutes = append(redirRoutes, Route{
matcherSets: []MatcherSet{ MatcherSets: []MatcherSet{
{ {
MatchProtocol("http"), MatchProtocol("http"),
MatchHost(domains), MatchHost(domains),
}, },
}, },
handlers: []MiddlewareHandler{ Handlers: []MiddlewareHandler{
StaticResponse{ StaticResponse{
StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
Headers: http.Header{ Headers: http.Header{

View File

@ -24,10 +24,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Log{})
Name: "http.handlers.log",
New: func() interface{} { return new(Log) },
})
} }
// Log implements a simple logging middleware. // Log implements a simple logging middleware.
@ -36,6 +33,14 @@ type Log struct {
counter int counter int
} }
// CaddyModule returns the Caddy module information.
func (Log) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.log",
New: func() caddy.Module { return new(Log) },
}
}
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
start := time.Now() start := time.Now()

View File

@ -19,16 +19,13 @@ import (
"strconv" "strconv"
"github.com/andybalholm/brotli" "github.com/andybalholm/brotli"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Brotli{})
Name: "http.encoders.brotli",
New: func() interface{} { return new(Brotli) },
})
} }
// Brotli can create brotli encoders. Note that brotli // Brotli can create brotli encoders. Note that brotli
@ -37,6 +34,14 @@ type Brotli struct {
Quality *int `json:"quality,omitempty"` Quality *int `json:"quality,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Brotli) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.encoders.brotli",
New: func() caddy.Module { return new(Brotli) },
}
}
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. // UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {

View File

@ -22,8 +22,25 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
func init() {
httpcaddyfile.RegisterHandlerDirective("encode", parseCaddyfile)
}
// TODO: This is a good example of why UnmarshalCaddyfile is still a good idea... hmm.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
enc := new(Encode)
err := enc.UnmarshalCaddyfile(h.Dispenser)
if err != nil {
return nil, err
}
return enc, nil
}
// TODO: Keep UnmarshalCaddyfile pattern?
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// encode [<matcher>] <formats...> { // encode [<matcher>] <formats...> {
@ -78,8 +95,5 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (enc Encode) Bucket() int { return 3 }
// Interface guard // Interface guard
var _ httpcaddyfile.HandlerDirective = (*Encode)(nil) var _ caddyfile.Unmarshaler = (*Encode)(nil)

View File

@ -35,10 +35,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Encode{})
Name: "http.handlers.encode",
New: func() interface{} { return new(Encode) },
})
} }
// Encode is a middleware which can encode responses. // Encode is a middleware which can encode responses.
@ -50,6 +47,14 @@ type Encode struct {
writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads... writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
} }
// CaddyModule returns the Caddy module information.
func (Encode) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.encode",
New: func() caddy.Module { return new(Encode) },
}
}
// Provision provisions enc. // Provision provisions enc.
func (enc *Encode) Provision(ctx caddy.Context) error { func (enc *Encode) Provision(ctx caddy.Context) error {
for modName, rawMsg := range enc.EncodingsRaw { for modName, rawMsg := range enc.EncodingsRaw {

View File

@ -20,16 +20,13 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Gzip{})
Name: "http.encoders.gzip",
New: func() interface{} { return new(Gzip) },
})
} }
// Gzip can create gzip encoders. // Gzip can create gzip encoders.
@ -37,6 +34,14 @@ type Gzip struct {
Level int `json:"level,omitempty"` Level int `json:"level,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Gzip) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.encoders.gzip",
New: func() caddy.Module { return new(Gzip) },
}
}
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. // UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {

View File

@ -15,22 +15,27 @@
package caddyzstd package caddyzstd
import ( import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode" "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Zstd{})
Name: "http.encoders.zstd",
New: func() interface{} { return new(Zstd) },
})
} }
// Zstd can create Zstandard encoders. // Zstd can create Zstandard encoders.
type Zstd struct{} type Zstd struct{}
// CaddyModule returns the Caddy module information.
func (Zstd) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.encoders.zstd",
New: func() caddy.Module { return new(Zstd) },
}
}
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. // UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil

View File

@ -15,59 +15,58 @@
package fileserver package fileserver
import ( import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "encoding/json"
"github.com/caddyserver/caddy/modules/caddyhttp/rewrite"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: func init() {
// httpcaddyfile.RegisterHandlerDirective("file_server", parseCaddyfile)
// file_server [<matcher>] [browse] { httpcaddyfile.RegisterDirective("try_files", parseTryFiles)
// hide <files...> }
// index <files...>
// browse [<template_file>] func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
// root <path> var fsrv FileServer
// }
// for h.Next() {
// If browse is given on the first line, it can't be used in the block also. args := h.RemainingArgs()
// The default root is the one given by the root directive.
func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
args := d.RemainingArgs()
switch len(args) { switch len(args) {
case 0: case 0:
case 1: case 1:
if args[0] != "browse" { if args[0] != "browse" {
return d.ArgErr() return nil, h.ArgErr()
} }
fsrv.Browse = new(Browse) fsrv.Browse = new(Browse)
default: default:
return d.ArgErr() return nil, h.ArgErr()
} }
for d.NextBlock() { for h.NextBlock() {
switch d.Val() { switch h.Val() {
case "hide": case "hide":
fsrv.Hide = d.RemainingArgs() fsrv.Hide = h.RemainingArgs()
if len(fsrv.Hide) == 0 { if len(fsrv.Hide) == 0 {
return d.ArgErr() return nil, h.ArgErr()
} }
case "index": case "index":
fsrv.IndexNames = d.RemainingArgs() fsrv.IndexNames = h.RemainingArgs()
if len(fsrv.Hide) == 0 { if len(fsrv.Hide) == 0 {
return d.ArgErr() return nil, h.ArgErr()
} }
case "root": case "root":
if !d.Args(&fsrv.Root) { if !h.Args(&fsrv.Root) {
return d.ArgErr() return nil, h.ArgErr()
} }
case "browse": case "browse":
if fsrv.Browse != nil { if fsrv.Browse != nil {
return d.Err("browsing is already configured") return nil, h.Err("browsing is already configured")
} }
fsrv.Browse = new(Browse) fsrv.Browse = new(Browse)
d.Args(&fsrv.Browse.TemplateFile) h.Args(&fsrv.Browse.TemplateFile)
default: default:
return d.Errf("unknown subdirective '%s'", d.Val()) return nil, h.Errf("unknown subdirective '%s'", h.Val())
} }
} }
} }
@ -77,11 +76,29 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
fsrv.Root = "{http.var.root}" fsrv.Root = "{http.var.root}"
} }
return nil return &fsrv, nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number. func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
func (fsrv FileServer) Bucket() int { return 7 } if !h.Next() {
return nil, h.ArgErr()
}
// Interface guard try := h.RemainingArgs()
var _ httpcaddyfile.HandlerDirective = (*FileServer)(nil) if len(try) == 0 {
return nil, h.ArgErr()
}
handler := rewrite.Rewrite{
URI: "{http.matchers.file.relative}{http.request.uri.query}",
}
matcherSet := map[string]json.RawMessage{
"file": h.JSON(MatchFile{
Root: "{http.var.root}",
TryFiles: try,
}, nil),
}
return h.NewRoute(matcherSet, handler), nil
}

View File

@ -20,16 +20,13 @@ import (
"os" "os"
"time" "time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(MatchFile{})
Name: "http.matchers.file",
New: func() interface{} { return new(MatchFile) },
})
} }
// MatchFile is an HTTP request matcher that can match // MatchFile is an HTTP request matcher that can match
@ -52,12 +49,20 @@ type MatchFile struct {
TryPolicy string `json:"try_policy,omitempty"` TryPolicy string `json:"try_policy,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (MatchFile) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.file",
New: func() caddy.Module { return new(MatchFile) },
}
}
// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax: // UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax:
// //
// file { // file {
// root <path> // root <path>
// try_files <files...> // try_files <files...>
// try_policy <first_exist|smallest_size|largest_size|most_recent_modified> // try_policy first_exist|smallest_size|largest_size|most_recent_modified
// } // }
// //
func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
@ -82,6 +87,9 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} }
} }
} }
if m.Root == "" {
m.Root = "{http.var.root}"
}
return nil return nil
} }
@ -121,7 +129,7 @@ func (m MatchFile) Match(r *http.Request) bool {
func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) { func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
root := repl.ReplaceAll(m.Root, "") root := repl.ReplaceAll(m.Root, ".")
// if list of files to try was omitted entirely, // if list of files to try was omitted entirely,
// assume URL path // assume URL path

View File

@ -36,10 +36,7 @@ import (
func init() { func init() {
weakrand.Seed(time.Now().UnixNano()) weakrand.Seed(time.Now().UnixNano())
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(FileServer{})
Name: "http.handlers.file_server",
New: func() interface{} { return new(FileServer) },
})
} }
// FileServer implements a static file server responder for Caddy. // FileServer implements a static file server responder for Caddy.
@ -50,6 +47,14 @@ type FileServer struct {
Browse *Browse `json:"browse,omitempty"` Browse *Browse `json:"browse,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (FileServer) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.file_server",
New: func() caddy.Module { return new(FileServer) },
}
}
// Provision sets up the static files responder. // Provision sets up the static files responder.
func (fsrv *FileServer) Provision(ctx caddy.Context) error { func (fsrv *FileServer) Provision(ctx caddy.Context) error {
if fsrv.IndexNames == nil { if fsrv.IndexNames == nil {

View File

@ -18,11 +18,15 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: func init() {
httpcaddyfile.RegisterHandlerDirective("headers", parseCaddyfile)
}
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// headers [<matcher>] [[+|-]<field> <value>] { // headers [<matcher>] [[+|-]<field> <value>] {
// [+][<field>] [<value>] // [+][<field>] [<value>]
@ -31,62 +35,57 @@ import (
// //
// Either a block can be opened or a single header field can be configured // Either a block can be opened or a single header field can be configured
// in the first line, but not both in the same directive. // in the first line, but not both in the same directive.
func (h *Headers) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
for d.Next() { hdr := new(Headers)
for h.Next() {
// first see if headers are in the initial line // first see if headers are in the initial line
var hasArgs bool var hasArgs bool
if d.NextArg() { if h.NextArg() {
hasArgs = true hasArgs = true
field := d.Val() field := h.Val()
d.NextArg() h.NextArg()
value := d.Val() value := h.Val()
h.processCaddyfileLine(field, value) processCaddyfileLine(hdr, field, value)
} }
// if not, they should be in a block // if not, they should be in a block
for d.NextBlock() { for h.NextBlock() {
if hasArgs { if hasArgs {
return d.Err("cannot specify headers in both arguments and block") return nil, h.Err("cannot specify headers in both arguments and block")
} }
field := d.Val() field := h.Val()
var value string var value string
if d.NextArg() { if h.NextArg() {
value = d.Val() value = h.Val()
} }
h.processCaddyfileLine(field, value) processCaddyfileLine(hdr, field, value)
} }
} }
return nil return hdr, nil
} }
func (h *Headers) processCaddyfileLine(field, value string) { func processCaddyfileLine(hdr *Headers, field, value string) {
if strings.HasPrefix(field, "+") { if strings.HasPrefix(field, "+") {
if h.Response == nil { if hdr.Response == nil {
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)} hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
} }
if h.Response.Add == nil { if hdr.Response.Add == nil {
h.Response.Add = make(http.Header) hdr.Response.Add = make(http.Header)
} }
h.Response.Add.Set(field[1:], value) hdr.Response.Add.Set(field[1:], value)
} else if strings.HasPrefix(field, "-") { } else if strings.HasPrefix(field, "-") {
if h.Response == nil { if hdr.Response == nil {
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)} hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
} }
h.Response.Delete = append(h.Response.Delete, field[1:]) hdr.Response.Delete = append(hdr.Response.Delete, field[1:])
h.Response.Deferred = true hdr.Response.Deferred = true
} else { } else {
if h.Response == nil { if hdr.Response == nil {
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)} hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
} }
if h.Response.Set == nil { if hdr.Response.Set == nil {
h.Response.Set = make(http.Header) hdr.Response.Set = make(http.Header)
} }
h.Response.Set.Set(field, value) hdr.Response.Set.Set(field, value)
} }
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (h Headers) Bucket() int { return 3 }
// Interface guard
var _ httpcaddyfile.HandlerDirective = (*Headers)(nil)

View File

@ -23,10 +23,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Headers{})
Name: "http.handlers.headers",
New: func() interface{} { return new(Headers) },
})
} }
// Headers is a middleware which can mutate HTTP headers. // Headers is a middleware which can mutate HTTP headers.
@ -35,6 +32,14 @@ type Headers struct {
Response *RespHeaderOps `json:"response,omitempty"` Response *RespHeaderOps `json:"response,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Headers) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.headers",
New: func() caddy.Module { return new(Headers) },
}
}
// HeaderOps defines some operations to // HeaderOps defines some operations to
// perform on HTTP headers. // perform on HTTP headers.
type HeaderOps struct { type HeaderOps struct {

View File

@ -28,16 +28,21 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Markdown{})
Name: "http.handlers.markdown",
New: func() interface{} { return new(Markdown) },
})
} }
// Markdown is a middleware for rendering a Markdown response body. // Markdown is a middleware for rendering a Markdown response body.
type Markdown struct { type Markdown struct {
} }
// CaddyModule returns the Caddy module information.
func (Markdown) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.markdown",
New: func() caddy.Module { return new(Markdown) },
}
}
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
buf := bufPool.Get().(*bytes.Buffer) buf := bufPool.Get().(*bytes.Buffer)
buf.Reset() buf.Reset()

View File

@ -80,50 +80,25 @@ type (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(MatchHost{})
caddy.RegisterModule(MatchPath{})
caddy.RegisterModule(MatchPathRE{})
caddy.RegisterModule(MatchMethod{})
caddy.RegisterModule(MatchQuery{})
caddy.RegisterModule(MatchHeader{})
caddy.RegisterModule(MatchHeaderRE{})
caddy.RegisterModule(new(MatchProtocol))
caddy.RegisterModule(MatchRemoteIP{})
caddy.RegisterModule(MatchNegate{})
caddy.RegisterModule(new(MatchStarlarkExpr))
}
// CaddyModule returns the Caddy module information.
func (MatchHost) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.host", Name: "http.matchers.host",
New: func() interface{} { return new(MatchHost) }, New: func() caddy.Module { return new(MatchHost) },
}) }
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.path",
New: func() interface{} { return new(MatchPath) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.path_regexp",
New: func() interface{} { return new(MatchPathRE) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.method",
New: func() interface{} { return new(MatchMethod) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.query",
New: func() interface{} { return new(MatchQuery) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.header",
New: func() interface{} { return new(MatchHeader) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.header_regexp",
New: func() interface{} { return new(MatchHeaderRE) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.protocol",
New: func() interface{} { return new(MatchProtocol) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.remote_ip",
New: func() interface{} { return new(MatchRemoteIP) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.not",
New: func() interface{} { return new(MatchNegate) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.starlark_expr",
New: func() interface{} { return new(MatchStarlarkExpr) },
})
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@ -165,6 +140,14 @@ outer:
return false return false
} }
// CaddyModule returns the Caddy module information.
func (MatchPath) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.path",
New: func() caddy.Module { return new(MatchPath) },
}
}
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPath) Match(r *http.Request) bool { func (m MatchPath) Match(r *http.Request) bool {
for _, matchPath := range m { for _, matchPath := range m {
@ -186,19 +169,39 @@ func (m MatchPath) Match(r *http.Request) bool {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
*m = d.RemainingArgs() for d.Next() {
*m = d.RemainingArgs()
}
return nil return nil
} }
// CaddyModule returns the Caddy module information.
func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.path_regexp",
New: func() caddy.Module { return new(MatchPathRE) },
}
}
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchPathRE) Match(r *http.Request) bool { func (m MatchPathRE) Match(r *http.Request) bool {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp") return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
} }
// CaddyModule returns the Caddy module information.
func (MatchMethod) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.method",
New: func() caddy.Module { return new(MatchMethod) },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
*m = d.RemainingArgs() for d.Next() {
*m = d.RemainingArgs()
}
return nil return nil
} }
@ -212,6 +215,14 @@ func (m MatchMethod) Match(r *http.Request) bool {
return false return false
} }
// CaddyModule returns the Caddy module information.
func (MatchQuery) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.query",
New: func() caddy.Module { return new(MatchQuery) },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
@ -237,6 +248,14 @@ func (m MatchQuery) Match(r *http.Request) bool {
return false return false
} }
// CaddyModule returns the Caddy module information.
func (MatchHeader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.header",
New: func() caddy.Module { return new(MatchHeader) },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
@ -270,6 +289,14 @@ func (m MatchHeader) Match(r *http.Request) bool {
return true return true
} }
// CaddyModule returns the Caddy module information.
func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.header_regexp",
New: func() caddy.Module { return new(MatchHeaderRE) },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if *m == nil { if *m == nil {
@ -319,6 +346,14 @@ func (m MatchHeaderRE) Validate() error {
return nil return nil
} }
// CaddyModule returns the Caddy module information.
func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.protocol",
New: func() caddy.Module { return new(MatchProtocol) },
}
}
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchProtocol) Match(r *http.Request) bool { func (m MatchProtocol) Match(r *http.Request) bool {
switch string(m) { switch string(m) {
@ -334,14 +369,24 @@ func (m MatchProtocol) Match(r *http.Request) bool {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
var proto string for d.Next() {
if !d.Args(&proto) { var proto string
return d.Err("expected exactly one protocol") if !d.Args(&proto) {
return d.Err("expected exactly one protocol")
}
*m = MatchProtocol(proto)
} }
*m = MatchProtocol(proto)
return nil return nil
} }
// CaddyModule returns the Caddy module information.
func (MatchNegate) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.not",
New: func() caddy.Module { return new(MatchNegate) },
}
}
// UnmarshalJSON unmarshals data into m's unexported map field. // UnmarshalJSON unmarshals data into m's unexported map field.
// This is done because we cannot embed the map directly into // This is done because we cannot embed the map directly into
// the struct, but we need a struct because we need another // the struct, but we need a struct because we need another
@ -375,9 +420,19 @@ func (m MatchNegate) Match(r *http.Request) bool {
return !m.matchers.Match(r) return !m.matchers.Match(r)
} }
// CaddyModule returns the Caddy module information.
func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.remote_ip",
New: func() caddy.Module { return new(MatchRemoteIP) },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
m.Ranges = d.RemainingArgs() for d.Next() {
m.Ranges = d.RemainingArgs()
}
return nil return nil
} }
@ -442,6 +497,14 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
return false return false
} }
// CaddyModule returns the Caddy module information.
func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
New: func() caddy.Module { return new(MatchStarlarkExpr) },
}
}
// Match returns true if r matches m. // Match returns true if r matches m.
func (m MatchStarlarkExpr) Match(r *http.Request) bool { func (m MatchStarlarkExpr) Match(r *http.Request) bool {
input := string(m) input := string(m)
@ -513,8 +576,17 @@ func (mre *MatchRegexp) Match(input string, repl caddy.Replacer, scope string) b
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if !d.Args(&mre.Name, &mre.Pattern) { for d.Next() {
return fmt.Errorf("missing arguments") args := d.RemainingArgs()
switch len(args) {
case 1:
mre.Pattern = args[0]
case 2:
mre.Name = args[0]
mre.Pattern = args[1]
default:
return d.ArgErr()
}
} }
return nil return nil
} }

View File

@ -22,10 +22,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(RequestBody{})
Name: "http.handlers.request_body",
New: func() interface{} { return new(RequestBody) },
})
} }
// RequestBody is a middleware for manipulating the request body. // RequestBody is a middleware for manipulating the request body.
@ -33,6 +30,14 @@ type RequestBody struct {
MaxSize int64 `json:"max_size,omitempty"` MaxSize int64 `json:"max_size,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (RequestBody) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.request_body", // TODO: better name for this?
New: func() caddy.Module { return new(RequestBody) },
}
}
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
if r.Body == nil { if r.Body == nil {
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)

View File

@ -15,39 +15,39 @@
package reverseproxy package reverseproxy
import ( import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// Register caddy module.
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(new(LoadBalanced))
Name: "http.handlers.reverse_proxy", httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile) // TODO: "proxy"?
New: func() interface{} { return new(LoadBalanced) },
})
} }
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: // CaddyModule returns the Caddy module information.
func (*LoadBalanced) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.reverse_proxy",
New: func() caddy.Module { return new(LoadBalanced) },
}
}
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// proxy [<matcher>] <to> // proxy [<matcher>] <to>
// //
// TODO: This needs to be finished. It definitely needs to be able to open a block... // TODO: This needs to be finished. It definitely needs to be able to open a block...
func (lb *LoadBalanced) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
for d.Next() { lb := new(LoadBalanced)
allTo := d.RemainingArgs() for h.Next() {
allTo := h.RemainingArgs()
if len(allTo) == 0 { if len(allTo) == 0 {
return d.ArgErr() return nil, h.ArgErr()
} }
for _, to := range allTo { for _, to := range allTo {
lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to}) lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to})
} }
} }
return nil return lb, nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (*LoadBalanced) Bucket() int { return 7 }
// Interface guard
var _ httpcaddyfile.HandlerDirective = (*LoadBalanced)(nil)

View File

@ -15,24 +15,23 @@
package rewrite package rewrite
import ( import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: func init() {
httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfile)
}
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// rewrite [<matcher>] <to> // rewrite [<matcher>] <to>
// //
// The <to> parameter becomes the new URI. // The <to> parameter becomes the new URI.
func (rewr *Rewrite) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
for d.Next() { var rewr Rewrite
rewr.URI = d.Val() for h.Next() {
rewr.URI = h.Val()
} }
return nil return rewr, nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (rewr Rewrite) Bucket() int { return 1 }
// Interface guard
var _ httpcaddyfile.HandlerDirective = (*Rewrite)(nil)

View File

@ -24,10 +24,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Rewrite{})
Name: "http.handlers.rewrite",
New: func() interface{} { return new(Rewrite) },
})
} }
// Rewrite is a middleware which can rewrite HTTP requests. // Rewrite is a middleware which can rewrite HTTP requests.
@ -37,6 +34,14 @@ type Rewrite struct {
Rehandle bool `json:"rehandle,omitempty"` Rehandle bool `json:"rehandle,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Rewrite) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.rewrite",
New: func() caddy.Module { return new(Rewrite) },
}
}
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
var rehandleNeeded bool var rehandleNeeded bool

View File

@ -26,33 +26,34 @@ import (
// middlewares, and a responder for handling HTTP // middlewares, and a responder for handling HTTP
// requests. // requests.
type Route struct { type Route struct {
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"` MatcherSetsRaw []map[string]json.RawMessage `json:"match,omitempty"`
Handle []json.RawMessage `json:"handle,omitempty"` HandlersRaw []json.RawMessage `json:"handle,omitempty"`
Terminal bool `json:"terminal,omitempty"` Terminal bool `json:"terminal,omitempty"`
// decoded values // decoded values
matcherSets []MatcherSet MatcherSets []MatcherSet `json:"-"`
handlers []MiddlewareHandler Handlers []MiddlewareHandler `json:"-"`
} }
// Empty returns true if the route has all zero/default values. // Empty returns true if the route has all zero/default values.
func (r Route) Empty() bool { func (r Route) Empty() bool {
return len(r.MatcherSets) == 0 && return len(r.MatcherSetsRaw) == 0 &&
len(r.Handle) == 0 && len(r.MatcherSets) == 0 &&
len(r.handlers) == 0 && len(r.HandlersRaw) == 0 &&
len(r.Handlers) == 0 &&
!r.Terminal && !r.Terminal &&
r.Group == "" r.Group == ""
} }
func (r Route) anyMatcherSetMatches(req *http.Request) bool { func (r Route) anyMatcherSetMatches(req *http.Request) bool {
for _, ms := range r.matcherSets { for _, ms := range r.MatcherSets {
if ms.Match(req) { if ms.Match(req) {
return true return true
} }
} }
// if no matchers, always match // if no matchers, always match
return len(r.matcherSets) == 0 return len(r.MatcherSets) == 0
} }
// MatcherSet is a set of matchers which // MatcherSet is a set of matchers which
@ -79,7 +80,7 @@ type RouteList []Route
func (routes RouteList) Provision(ctx caddy.Context) error { func (routes RouteList) Provision(ctx caddy.Context) error {
for i, route := range routes { for i, route := range routes {
// matchers // matchers
for _, matcherSet := range route.MatcherSets { for _, matcherSet := range route.MatcherSetsRaw {
var matchers MatcherSet var matchers MatcherSet
for modName, rawMsg := range matcherSet { for modName, rawMsg := range matcherSet {
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
@ -88,19 +89,19 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
} }
matchers = append(matchers, val.(RequestMatcher)) matchers = append(matchers, val.(RequestMatcher))
} }
routes[i].matcherSets = append(routes[i].matcherSets, matchers) routes[i].MatcherSets = append(routes[i].MatcherSets, matchers)
} }
routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help? routes[i].MatcherSetsRaw = nil // allow GC to deallocate - TODO: Does this help?
// handlers // handlers
for j, rawMsg := range route.Handle { for j, rawMsg := range route.HandlersRaw {
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg) mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
if err != nil { if err != nil {
return fmt.Errorf("loading handler module in position %d: %v", j, err) return fmt.Errorf("loading handler module in position %d: %v", j, err)
} }
routes[i].handlers = append(routes[i].handlers, mh.(MiddlewareHandler)) routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler))
} }
routes[i].Handle = nil // allow GC to deallocate - TODO: Does this help? routes[i].HandlersRaw = nil // allow GC to deallocate - TODO: Does this help?
} }
return nil return nil
} }
@ -135,7 +136,7 @@ func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
} }
// apply the rest of the route // apply the rest of the route
for _, mh := range route.handlers { for _, mh := range route.Handlers {
// we have to be sure to wrap mh outside // we have to be sure to wrap mh outside
// of our current stack frame so that the // of our current stack frame so that the
// reference to this mh isn't overwritten // reference to this mh isn't overwritten

View File

@ -23,10 +23,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(StaticError{})
Name: "http.handlers.error",
New: func() interface{} { return new(StaticError) },
})
} }
// StaticError implements a simple handler that returns an error. // StaticError implements a simple handler that returns an error.
@ -35,6 +32,14 @@ type StaticError struct {
StatusCode WeakString `json:"status_code,omitempty"` StatusCode WeakString `json:"status_code,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (StaticError) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.error",
New: func() caddy.Module { return new(StaticError) },
}
}
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)

View File

@ -24,10 +24,8 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(StaticResponse{})
Name: "http.handlers.static_response", // TODO: Caddyfile directive
New: func() interface{} { return new(StaticResponse) },
})
} }
// StaticResponse implements a simple responder for static responses. // StaticResponse implements a simple responder for static responses.
@ -38,6 +36,14 @@ type StaticResponse struct {
Close bool `json:"close,omitempty"` Close bool `json:"close,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (StaticResponse) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.static_response",
New: func() caddy.Module { return new(StaticResponse) },
}
}
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: // UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// static_response [<matcher>] <status> { // static_response [<matcher>] <status> {
@ -71,9 +77,6 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (StaticResponse) Bucket() int { return 7 }
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)

View File

@ -22,10 +22,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Subroute{})
Name: "http.handlers.subroute",
New: func() interface{} { return new(Subroute) },
})
} }
// Subroute implements a handler that compiles and executes routes. // Subroute implements a handler that compiles and executes routes.
@ -37,6 +34,14 @@ type Subroute struct {
Routes RouteList `json:"routes,omitempty"` Routes RouteList `json:"routes,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Subroute) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.subroute",
New: func() caddy.Module { return new(Subroute) },
}
}
// Provision sets up subrouting. // Provision sets up subrouting.
func (sr *Subroute) Provision(ctx caddy.Context) error { func (sr *Subroute) Provision(ctx caddy.Context) error {
if sr.Routes != nil { if sr.Routes != nil {

View File

@ -15,11 +15,15 @@
package templates package templates
import ( import (
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: func init() {
httpcaddyfile.RegisterHandlerDirective("templates", parseCaddyfile)
}
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
// //
// templates [<matcher>] { // templates [<matcher>] {
// mime <types...> // mime <types...>
@ -27,23 +31,24 @@ import (
// root <path> // root <path>
// } // }
// //
func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
for d.Next() { t := new(Templates)
for d.NextBlock() { for h.Next() {
switch d.Val() { for h.NextBlock() {
switch h.Val() {
case "mime": case "mime":
t.MIMETypes = d.RemainingArgs() t.MIMETypes = h.RemainingArgs()
if len(t.MIMETypes) == 0 { if len(t.MIMETypes) == 0 {
return d.ArgErr() return nil, h.ArgErr()
} }
case "between": case "between":
t.Delimiters = d.RemainingArgs() t.Delimiters = h.RemainingArgs()
if len(t.Delimiters) != 2 { if len(t.Delimiters) != 2 {
return d.ArgErr() return nil, h.ArgErr()
} }
case "root": case "root":
if !d.Args(&t.IncludeRoot) { if !h.Args(&t.IncludeRoot) {
return d.ArgErr() return nil, h.ArgErr()
} }
} }
} }
@ -53,11 +58,5 @@ func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
t.IncludeRoot = "{http.var.root}" t.IncludeRoot = "{http.var.root}"
} }
return nil return t, nil
} }
// Bucket returns the HTTP Caddyfile handler bucket number.
func (t Templates) Bucket() int { return 5 }
// Interface guard
var _ httpcaddyfile.HandlerDirective = (*Templates)(nil)

View File

@ -27,10 +27,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(Templates{})
Name: "http.handlers.templates",
New: func() interface{} { return new(Templates) },
})
} }
// Templates is a middleware which execute response bodies as templates. // Templates is a middleware which execute response bodies as templates.
@ -40,6 +37,14 @@ type Templates struct {
Delimiters []string `json:"delimiters,omitempty"` Delimiters []string `json:"delimiters,omitempty"`
} }
// CaddyModule returns the Caddy module information.
func (Templates) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.templates",
New: func() caddy.Module { return new(Templates) },
}
}
// Provision provisions t. // Provision provisions t.
func (t *Templates) Provision(ctx caddy.Context) error { func (t *Templates) Provision(ctx caddy.Context) error {
if t.MIMETypes == nil { if t.MIMETypes == nil {

View File

@ -21,20 +21,22 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(VarsMiddleware{})
Name: "http.handlers.vars", caddy.RegisterModule(VarsMatcher{})
New: func() interface{} { return new(VarsMiddleware) },
})
caddy.RegisterModule(caddy.Module{
Name: "http.matchers.vars",
New: func() interface{} { return new(VarsMiddleware) },
})
} }
// VarsMiddleware is an HTTP middleware which sets variables // VarsMiddleware is an HTTP middleware which sets variables
// in the context, mainly for use by placeholders. // in the context, mainly for use by placeholders.
type VarsMiddleware map[string]string type VarsMiddleware map[string]string
// CaddyModule returns the Caddy module information.
func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.handlers.vars",
New: func() caddy.Module { return new(VarsMiddleware) },
}
}
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error { func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
vars := r.Context().Value(VarCtxKey).(map[string]interface{}) vars := r.Context().Value(VarCtxKey).(map[string]interface{})
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
@ -50,6 +52,14 @@ func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
// requests based on variables in the context. // requests based on variables in the context.
type VarsMatcher map[string]string type VarsMatcher map[string]string
// CaddyModule returns the Caddy module information.
func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "http.matchers.vars",
New: func() caddy.Module { return new(VarsMatcher) },
}
}
// Match matches a request based on variables in the context. // Match matches a request based on variables in the context.
func (m VarsMatcher) Match(r *http.Request) bool { func (m VarsMatcher) Match(r *http.Request) bool {
vars := r.Context().Value(VarCtxKey).(map[string]string) vars := r.Context().Value(VarCtxKey).(map[string]string)

View File

@ -28,10 +28,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(ACMEManagerMaker{})
Name: "tls.management.acme",
New: func() interface{} { return new(ACMEManagerMaker) },
})
} }
// ACMEManagerMaker makes an ACME manager // ACMEManagerMaker makes an ACME manager
@ -57,9 +54,17 @@ type ACMEManagerMaker struct {
keyType certcrypto.KeyType keyType certcrypto.KeyType
} }
// newManager is a no-op to satisfy the ManagerMaker interface, // CaddyModule returns the Caddy module information.
func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls.management.acme",
New: func() caddy.Module { return new(ACMEManagerMaker) },
}
}
// NewManager is a no-op to satisfy the ManagerMaker interface,
// because this manager type is a special case. // because this manager type is a special case.
func (m *ACMEManagerMaker) newManager(interactive bool) (certmagic.Manager, error) { func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error) {
return nil, nil return nil, nil
} }
@ -203,4 +208,4 @@ func onDemandAskRequest(ask string, name string) error {
} }
// Interface guard // Interface guard
var _ managerMaker = (*ACMEManagerMaker)(nil) var _ ManagerMaker = (*ACMEManagerMaker)(nil)

View File

@ -23,15 +23,20 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(FileLoader{})
Name: "tls.certificates.load_files",
New: func() interface{} { return FileLoader{} },
})
} }
// FileLoader loads certificates and their associated keys from disk. // FileLoader loads certificates and their associated keys from disk.
type FileLoader []CertKeyFilePair type FileLoader []CertKeyFilePair
// CaddyModule returns the Caddy module information.
func (FileLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls.certificates.load_files",
New: func() caddy.Module { return new(FileLoader) },
}
}
// CertKeyFilePair pairs certificate and key file names along with their // CertKeyFilePair pairs certificate and key file names along with their
// encoding format so that they can be loaded from disk. // encoding format so that they can be loaded from disk.
type CertKeyFilePair struct { type CertKeyFilePair struct {

View File

@ -28,10 +28,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(FolderLoader{})
Name: "tls.certificates.load_folders",
New: func() interface{} { return FolderLoader{} },
})
} }
// FolderLoader loads certificates and their associated keys from disk // FolderLoader loads certificates and their associated keys from disk
@ -39,6 +36,14 @@ func init() {
// files which contain both a certificate and a key. // files which contain both a certificate and a key.
type FolderLoader []string type FolderLoader []string
// CaddyModule returns the Caddy module information.
func (FolderLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls.certificates.load_folders",
New: func() caddy.Module { return new(FolderLoader) },
}
}
// LoadCertificates loads all the certificates+keys in the directories // LoadCertificates loads all the certificates+keys in the directories
// listed in fl from all files ending with .pem. This method of loading // listed in fl from all files ending with .pem. This method of loading
// certificates expects the certificate and key to be bundled into the // certificates expects the certificate and key to be bundled into the

View File

@ -20,14 +20,19 @@ import (
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
) )
func init() {
caddy.RegisterModule(MatchServerName{})
}
// MatchServerName matches based on SNI. // MatchServerName matches based on SNI.
type MatchServerName []string type MatchServerName []string
func init() { // CaddyModule returns the Caddy module information.
caddy.RegisterModule(caddy.Module{ func (MatchServerName) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls.handshake_match.sni", Name: "tls.handshake_match.sni",
New: func() interface{} { return MatchServerName{} }, New: func() caddy.Module { return new(MatchServerName) },
}) }
} }
// Match matches hello based on SNI. // Match matches hello based on SNI.

View File

@ -24,10 +24,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(standardSTEKProvider{})
Name: "tls.stek.standard",
New: func() interface{} { return new(standardSTEKProvider) },
})
} }
type standardSTEKProvider struct { type standardSTEKProvider struct {
@ -35,6 +32,14 @@ type standardSTEKProvider struct {
timer *time.Timer timer *time.Timer
} }
// CaddyModule returns the Caddy module information.
func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls.stek.standard",
New: func() caddy.Module { return new(standardSTEKProvider) },
}
}
// Initialize sets the configuration for s and returns the starting keys. // Initialize sets the configuration for s and returns the starting keys.
func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) { func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) {
// keep a reference to the config; we'll need it when rotating keys // keep a reference to the config; we'll need it when rotating keys

View File

@ -30,10 +30,7 @@ import (
) )
func init() { func init() {
caddy.RegisterModule(caddy.Module{ caddy.RegisterModule(TLS{})
Name: "tls",
New: func() interface{} { return new(TLS) },
})
// opt-in TLS 1.3 for Go1.12 // opt-in TLS 1.3 for Go1.12
// TODO: remove this line when Go1.13 is released. // TODO: remove this line when Go1.13 is released.
@ -53,6 +50,14 @@ type TLS struct {
ctx caddy.Context ctx caddy.Context
} }
// CaddyModule returns the Caddy module information.
func (TLS) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
Name: "tls",
New: func() caddy.Module { return new(TLS) },
}
}
// Provision sets up the configuration for the TLS app. // Provision sets up the configuration for the TLS app.
func (t *TLS) Provision(ctx caddy.Context) error { func (t *TLS) Provision(ctx caddy.Context) error {
t.ctx = ctx t.ctx = ctx
@ -73,7 +78,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
if err != nil { if err != nil {
return fmt.Errorf("loading TLS automation management module: %s", err) return fmt.Errorf("loading TLS automation management module: %s", err)
} }
t.Automation.Policies[i].Management = val.(managerMaker) t.Automation.Policies[i].Management = val.(ManagerMaker)
t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help? t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help?
} }
@ -237,7 +242,7 @@ type AutomationPolicy struct {
Hosts []string `json:"hosts,omitempty"` Hosts []string `json:"hosts,omitempty"`
ManagementRaw json.RawMessage `json:"management,omitempty"` ManagementRaw json.RawMessage `json:"management,omitempty"`
Management managerMaker `json:"-"` Management ManagerMaker `json:"-"`
} }
// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand // makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
@ -252,7 +257,7 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
} }
return certmagic.Config{ return certmagic.Config{
NewManager: ap.Management.newManager, NewManager: ap.Management.NewManager,
} }
} }
@ -290,9 +295,9 @@ type RateLimit struct {
Burst int `json:"burst,omitempty"` Burst int `json:"burst,omitempty"`
} }
// managerMaker makes a certificate manager. // ManagerMaker makes a certificate manager.
type managerMaker interface { type ManagerMaker interface {
newManager(interactive bool) (certmagic.Manager, error) NewManager(interactive bool) (certmagic.Manager, error)
} }
// These perpetual values are used for on-demand TLS. // These perpetual values are used for on-demand TLS.

View File

@ -21,7 +21,7 @@ import (
func TestGetModules(t *testing.T) { func TestGetModules(t *testing.T) {
modulesMu.Lock() modulesMu.Lock()
modules = map[string]Module{ modules = map[string]ModuleInfo{
"a": {Name: "a"}, "a": {Name: "a"},
"a.b": {Name: "a.b"}, "a.b": {Name: "a.b"},
"a.b.c": {Name: "a.b.c"}, "a.b.c": {Name: "a.b.c"},
@ -38,11 +38,11 @@ func TestGetModules(t *testing.T) {
for i, tc := range []struct { for i, tc := range []struct {
input string input string
expect []Module expect []ModuleInfo
}{ }{
{ {
input: "", input: "",
expect: []Module{ expect: []ModuleInfo{
{Name: "a"}, {Name: "a"},
{Name: "b"}, {Name: "b"},
{Name: "c"}, {Name: "c"},
@ -50,7 +50,7 @@ func TestGetModules(t *testing.T) {
}, },
{ {
input: "a", input: "a",
expect: []Module{ expect: []ModuleInfo{
{Name: "a.b"}, {Name: "a.b"},
{Name: "a.c"}, {Name: "a.c"},
{Name: "a.d"}, {Name: "a.d"},
@ -58,7 +58,7 @@ func TestGetModules(t *testing.T) {
}, },
{ {
input: "a.b", input: "a.b",
expect: []Module{ expect: []ModuleInfo{
{Name: "a.b.c"}, {Name: "a.b.c"},
{Name: "a.b.cd"}, {Name: "a.b.cd"},
}, },
@ -68,7 +68,7 @@ func TestGetModules(t *testing.T) {
}, },
{ {
input: "b", input: "b",
expect: []Module{ expect: []ModuleInfo{
{Name: "b.a"}, {Name: "b.a"},
{Name: "b.b"}, {Name: "b.b"},
}, },

View File

@ -23,10 +23,7 @@ import (
) )
func init() { func init() {
RegisterModule(Module{ RegisterModule(fileStorage{})
Name: "caddy.storage.file_system",
New: func() interface{} { return new(fileStorage) },
})
} }
// StorageConverter is a type that can convert itself // StorageConverter is a type that can convert itself
@ -43,6 +40,14 @@ type fileStorage struct {
Root string `json:"root"` Root string `json:"root"`
} }
// CaddyModule returns the Caddy module information.
func (fileStorage) CaddyModule() ModuleInfo {
return ModuleInfo{
Name: "caddy.storage.file_system",
New: func() Module { return new(fileStorage) },
}
}
func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) { func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) {
return &certmagic.FileStorage{Path: s.Root}, nil return &certmagic.FileStorage{Path: s.Root}, nil
} }