summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--admin.go11
-rw-r--r--caddyconfig/caddyfile/adapter.go8
-rwxr-xr-xcaddyconfig/caddyfile/dispenser.go30
-rwxr-xr-xcaddyconfig/caddyfile/parse.go112
-rwxr-xr-xcaddyconfig/caddyfile/parse_test.go181
-rw-r--r--caddyconfig/httpcaddyfile/addresses.go162
-rw-r--r--caddyconfig/httpcaddyfile/addresses_test.go121
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go356
-rw-r--r--caddyconfig/httpcaddyfile/directives.go182
-rw-r--r--caddyconfig/httpcaddyfile/handlers.go36
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go369
-rw-r--r--context.go11
-rw-r--r--modules.go107
-rw-r--r--modules/caddyhttp/caddyhttp.go19
-rw-r--r--modules/caddyhttp/caddylog/log.go13
-rw-r--r--modules/caddyhttp/encode/brotli/brotli.go15
-rw-r--r--modules/caddyhttp/encode/caddyfile.go22
-rw-r--r--modules/caddyhttp/encode/encode.go13
-rw-r--r--modules/caddyhttp/encode/gzip/gzip.go15
-rw-r--r--modules/caddyhttp/encode/zstd/zstd.go15
-rw-r--r--modules/caddyhttp/fileserver/caddyfile.go83
-rw-r--r--modules/caddyhttp/fileserver/matcher.go22
-rw-r--r--modules/caddyhttp/fileserver/staticfiles.go13
-rw-r--r--modules/caddyhttp/headers/caddyfile.go73
-rw-r--r--modules/caddyhttp/headers/headers.go13
-rw-r--r--modules/caddyhttp/markdown/markdown.go13
-rw-r--r--modules/caddyhttp/matchers.go176
-rw-r--r--modules/caddyhttp/requestbody/requestbody.go13
-rwxr-xr-xmodules/caddyhttp/reverseproxy/module.go36
-rw-r--r--modules/caddyhttp/rewrite/caddyfile.go23
-rw-r--r--modules/caddyhttp/rewrite/rewrite.go13
-rw-r--r--modules/caddyhttp/routes.go37
-rw-r--r--modules/caddyhttp/staticerror.go13
-rw-r--r--modules/caddyhttp/staticresp.go17
-rw-r--r--modules/caddyhttp/subroute.go13
-rw-r--r--modules/caddyhttp/templates/caddyfile.go37
-rw-r--r--modules/caddyhttp/templates/templates.go13
-rw-r--r--modules/caddyhttp/vars.go26
-rw-r--r--modules/caddytls/acmemanager.go19
-rw-r--r--modules/caddytls/fileloader.go13
-rw-r--r--modules/caddytls/folderloader.go13
-rw-r--r--modules/caddytls/matchers.go13
-rw-r--r--modules/caddytls/standardstek/stek.go13
-rw-r--r--modules/caddytls/tls.go25
-rw-r--r--modules_test.go12
-rw-r--r--storage.go13
46 files changed, 1480 insertions, 1073 deletions
diff --git a/admin.go b/admin.go
index ba3f060..7799913 100644
--- a/admin.go
+++ b/admin.go
@@ -95,9 +95,9 @@ func StartAdmin(initialConfigJSON []byte) error {
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
///// END PPROF STUFF //////
- for _, m := range GetModules("admin") {
- routes := m.New().([]AdminRoute)
- for _, route := range routes {
+ for _, m := range GetModules("admin.routers") {
+ adminrtr := m.New().(AdminRouter)
+ for _, route := range adminrtr.Routes() {
mux.Handle(route.Pattern, route)
}
}
@@ -146,6 +146,11 @@ func StopAdmin() error {
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.
type AdminRoute struct {
http.Handler
diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go
index ab4905a..377f77b 100644
--- a/caddyconfig/caddyfile/adapter.go
+++ b/caddyconfig/caddyfile/adapter.go
@@ -37,14 +37,12 @@ func (a Adapter) Adapt(body []byte, options map[string]string) ([]byte, []caddyc
options = make(map[string]string)
}
- directives := a.ServerType.ValidDirectives()
-
filename := options["filename"]
if filename == "" {
filename = "Caddyfile"
}
- serverBlocks, err := Parse(filename, bytes.NewReader(body), directives)
+ serverBlocks, err := Parse(filename, bytes.NewReader(body))
if err != nil {
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.
type ServerType interface {
- // ValidDirectives returns a list of the
- // server type's recognized directives.
- ValidDirectives() []string
-
// Setup takes the server blocks which
// contain tokens, as well as options
// (e.g. CLI flags) and creates a Caddy
diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go
index 1cf5d04..0d2c789 100755
--- a/caddyconfig/caddyfile/dispenser.go
+++ b/caddyconfig/caddyfile/dispenser.go
@@ -31,6 +31,7 @@ type Dispenser struct {
}
// 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 {
return &Dispenser{
filename: filename,
@@ -51,15 +52,15 @@ func (d *Dispenser) Next() bool {
}
// Prev moves to the previous token. It does the inverse
-// of Next(). Generally, this should only be used in
-// special cases such as deleting a token from the slice
-// that d is iterating. In that case, without using Prev(),
-// the dispenser would be pointing at the wrong token since
-// deleting a token implicitly advances the cursor.
+// of Next(), except this function may decrement the cursor
+// to -1 so that the next call to Next() points to the
+// first token; this allows dispensing to "start over". This
+// method returns true if the cursor ends up pointing to a
+// valid token.
func (d *Dispenser) Prev() bool {
- if d.cursor > 0 {
+ if d.cursor > -1 {
d.cursor--
- return true
+ return d.cursor > -1
}
return false
}
@@ -223,8 +224,7 @@ func (d *Dispenser) RemainingArgs() []string {
// "directive" whether that be to the end of the line or
// the end of a block that starts at the end of the line.
func (d *Dispenser) NewFromNextTokens() *Dispenser {
- var tkns []Token
- tkns = append(tkns, d.Token())
+ tkns := []Token{d.Token()}
for d.NextArg() {
tkns = append(tkns, d.Token())
}
@@ -245,10 +245,14 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
// Token returns the current 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 d.tokens[d.cursor]
+ return d.tokens[cursor]
}
// Cursor returns the current cursor (token index).
@@ -256,6 +260,10 @@ func (d *Dispenser) Cursor() int {
return d.cursor
}
+func (d *Dispenser) Reset() {
+ d.cursor = -1
+}
+
// ArgErr returns an argument error, meaning that another
// argument was expected but not found. In other words,
// a line break or open curly brace was encountered instead of
diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go
index cc91e3d..e5b25fc 100755
--- a/caddyconfig/caddyfile/parse.go
+++ b/caddyconfig/caddyfile/parse.go
@@ -28,12 +28,12 @@ import (
// Directives that do not appear in validDirectives will cause
// an error. If you do not want to check for valid directives,
// 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)
if err != nil {
return nil, err
}
- p := parser{Dispenser: NewDispenser(filename, tokens), validDirectives: validDirectives}
+ p := parser{Dispenser: NewDispenser(filename, tokens)}
return p.parseAll()
}
@@ -56,9 +56,9 @@ func allTokens(input io.Reader) ([]Token, error) {
type parser struct {
*Dispenser
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
definedSnippets map[string][]Token
+ nesting int
}
func (p *parser) parseAll() ([]ServerBlock, error) {
@@ -72,14 +72,16 @@ func (p *parser) parseAll() ([]ServerBlock, error) {
if len(p.block.Keys) > 0 {
blocks = append(blocks, p.block)
}
+ if p.nesting > 0 {
+ return blocks, p.EOFErr()
+ }
}
return blocks, nil
}
func (p *parser) parseOne() error {
- p.block = ServerBlock{Tokens: make(map[string][]Token)}
-
+ p.block = ServerBlock{}
return p.begin()
}
@@ -186,7 +188,7 @@ func (p *parser) blockContents() error {
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 {
err = p.closeCurlyBrace()
if err != nil {
@@ -205,6 +207,7 @@ func (p *parser) directives() error {
for p.Next() {
// end of server block
if p.Val() == "}" {
+ // p.nesting has already been decremented
break
}
@@ -218,11 +221,15 @@ func (p *parser) directives() error {
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 {
return err
}
}
+
return nil
}
@@ -345,25 +352,24 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
- dir := replaceEnvVars(p.Val())
- nesting := 0
+ // evaluate any env vars in directive token
+ p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
- if !p.validDirective(dir) {
- return p.Errf("Unknown directive '%s'", dir)
- }
+ // a segment is a list of tokens associated with this directive
+ var segment Segment
- // The directive itself is appended as a relevant token
- p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
+ // the directive itself is appended as a relevant token
+ segment = append(segment, p.Token())
for p.Next() {
if p.Val() == "{" {
- nesting++
- } else if p.isNewLine() && nesting == 0 {
+ p.nesting++
+ } else if p.isNewLine() && p.nesting == 0 {
p.cursor-- // read too far
break
- } else if p.Val() == "}" && nesting > 0 {
- nesting--
- } else if p.Val() == "}" && nesting == 0 {
+ } else if p.Val() == "}" && p.nesting > 0 {
+ p.nesting--
+ } else if p.Val() == "}" && p.nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace")
} else if p.Val() == "import" && p.isNewLine() {
if err := p.doImport(); err != nil {
@@ -373,12 +379,15 @@ func (p *parser) directive() error {
continue
}
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 nil
}
@@ -404,19 +413,6 @@ func (p *parser) closeCurlyBrace() error {
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
// and understands both the $UNIX and %WINDOWS% syntaxes.
func replaceEnvVars(s string) string {
@@ -447,13 +443,6 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
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) {
keys := p.block.Keys
// 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() == "{" {
+ p.nesting++
count++
}
tokens = append(tokens, p.tokens[p.cursor])
@@ -490,3 +480,43 @@ func (p *parser) snippetTokens() ([]Token, error) {
}
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)
+}
diff --git a/caddyconfig/caddyfile/parse_test.go b/caddyconfig/caddyfile/parse_test.go
index 654c68d..19959de 100755
--- a/caddyconfig/caddyfile/parse_test.go
+++ b/caddyconfig/caddyfile/parse_test.go
@@ -22,6 +22,8 @@ import (
"testing"
)
+// TODO: re-enable all tests
+
func TestAllTokens(t *testing.T) {
input := strings.NewReader("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
@@ -53,84 +55,67 @@ func TestParseOneAndImport(t *testing.T) {
input string
shouldErr bool
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",
- }, map[string]int{}},
+ }, []int{}},
{`localhost
dir1`, false, []string{
"localhost",
- }, map[string]int{
- "dir1": 1,
- }},
+ }, []int{1}},
{`localhost:1234
dir1 foo bar`, false, []string{
"localhost:1234",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3},
+ },
{`localhost {
dir1
}`, false, []string{
"localhost",
- }, map[string]int{
- "dir1": 1,
- }},
+ }, []int{1}},
{`localhost:1234 {
dir1 foo bar
dir2
}`, false, []string{
"localhost:1234",
- }, map[string]int{
- "dir1": 3,
- "dir2": 1,
- }},
+ }, []int{3, 1}},
{`http://localhost https://localhost
dir1 foo bar`, false, []string{
"http://localhost",
"https://localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3}},
{`http://localhost https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3}},
{`http://localhost, https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3}},
{`http://localhost, {
}`, true, []string{
"http://localhost",
- }, map[string]int{}},
+ }, []int{}},
{`host1:80, http://host2.com
dir1 foo bar
dir2 baz`, false, []string{
"host1:80",
"http://host2.com",
- }, map[string]int{
- "dir1": 3,
- "dir2": 2,
- }},
+ }, []int{3, 2}},
{`http://host1.com,
http://host2.com,
@@ -138,7 +123,7 @@ func TestParseOneAndImport(t *testing.T) {
"http://host1.com",
"http://host2.com",
"https://host3.com",
- }, map[string]int{}},
+ }, []int{}},
{`http://host1.com:1234, https://host2.com
dir1 foo {
@@ -147,10 +132,7 @@ func TestParseOneAndImport(t *testing.T) {
dir2`, false, []string{
"http://host1.com:1234",
"https://host2.com",
- }, map[string]int{
- "dir1": 6,
- "dir2": 1,
- }},
+ }, []int{6, 1}},
{`127.0.0.1
dir1 {
@@ -160,34 +142,25 @@ func TestParseOneAndImport(t *testing.T) {
foo bar
}`, false, []string{
"127.0.0.1",
- }, map[string]int{
- "dir1": 5,
- "dir2": 5,
- }},
+ }, []int{5, 5}},
{`localhost
dir1 {
foo`, true, []string{
"localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3}},
{`localhost
dir1 {
}`, false, []string{
"localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{3}},
{`localhost
dir1 {
} }`, true, []string{
"localhost",
- }, map[string]int{
- "dir1": 3,
- }},
+ }, []int{}},
{`localhost
dir1 {
@@ -197,48 +170,38 @@ func TestParseOneAndImport(t *testing.T) {
}
dir2 foo bar`, false, []string{
"localhost",
- }, map[string]int{
- "dir1": 7,
- "dir2": 3,
- }},
+ }, []int{7, 3}},
- {``, false, []string{}, map[string]int{}},
+ {``, false, []string{}, []int{}},
{`localhost
dir1 arg1
import testdata/import_test1.txt`, false, []string{
"localhost",
- }, map[string]int{
- "dir1": 2,
- "dir2": 3,
- "dir3": 1,
- }},
+ }, []int{2, 3, 1}},
{`import testdata/import_test2.txt`, false, []string{
"host1",
- }, map[string]int{
- "dir1": 1,
- "dir2": 2,
- }},
+ }, []int{1, 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!
- {`import }{$"`, true, []string{}, map[string]int{}},
- {`import /*/*.txt`, true, []string{}, map[string]int{}},
- {`import /???/?*?o`, true, []string{}, map[string]int{}},
- {`import /??`, true, []string{}, map[string]int{}},
- {`import /[a-z]`, true, []string{}, map[string]int{}},
- {`import {$}`, true, []string{}, map[string]int{}},
- {`import {%}`, true, []string{}, map[string]int{}},
- {`import {$$}`, true, []string{}, map[string]int{}},
- {`import {%%}`, true, []string{}, map[string]int{}},
+ {`import }{$"`, true, []string{}, []int{}},
+ {`import /*/*.txt`, true, []string{}, []int{}},
+ {`import /???/?*?o`, true, []string{}, []int{}},
+ {`import /??`, true, []string{}, []int{}},
+ {`import /[a-z]`, true, []string{}, []int{}},
+ {`import {$}`, true, []string{}, []int{}},
+ {`import {%}`, true, []string{}, []int{}},
+ {`import {$$}`, true, []string{}, []int{}},
+ {`import {%%}`, true, []string{}, []int{}},
} {
result, err := testParseOne(test.input)
@@ -261,15 +224,16 @@ func TestParseOneAndImport(t *testing.T) {
}
}
- if len(result.Tokens) != len(test.tokens) {
- t.Errorf("Test %d: Expected %d directives, had %d",
- i, len(test.tokens), len(result.Tokens))
+ if len(result.Segments) != len(test.numTokens) {
+ t.Errorf("Test %d: Expected %d segments, had %d",
+ i, len(test.numTokens), len(result.Segments))
continue
}
- for directive, tokens := range result.Tokens {
- if len(tokens) != test.tokens[directive] {
- t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
- i, directive, test.tokens[directive], len(tokens))
+
+ for j, seg := range result.Segments {
+ if len(seg) != test.numTokens[j] {
+ t.Errorf("Test %d, segment %d: Expected %d tokens, counted %d",
+ i, j, test.numTokens[j], len(seg))
continue
}
}
@@ -289,12 +253,12 @@ func TestRecursiveImport(t *testing.T) {
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
return false
}
- if len(got.Tokens) != 2 {
- t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
+ if len(got.Segments) != 2 {
+ t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false
}
- if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 {
- t.Errorf("got unexpect tokens: %v", got.Tokens)
+ if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
+ t.Errorf("got unexpect tokens: %v", got.Segments)
return false
}
return true
@@ -384,12 +348,12 @@ func TestDirectiveImport(t *testing.T) {
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
return false
}
- if len(got.Tokens) != 2 {
- t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
+ if len(got.Segments) != 2 {
+ t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false
}
- if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 {
- t.Errorf("got unexpect tokens: %v", got.Tokens)
+ if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
+ t.Errorf("got unexpect tokens: %v", got.Segments)
return false
}
return true
@@ -557,21 +521,21 @@ func TestEnvironmentReplacement(t *testing.T) {
if actual, expected := blocks[0].Keys[0], ":8080"; 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)
}
// combined windows env vars in argument
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
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)
}
// malformed env var (windows)
p = testParser(":1234\ndir1 {%ADDRESS}")
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)
}
@@ -585,22 +549,18 @@ func TestEnvironmentReplacement(t *testing.T) {
// in quoted field
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
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)
}
// after end token
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
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)
}
}
-func testParser(input string) parser {
- return parser{Dispenser: newTestDispenser(input)}
-}
-
func TestSnippets(t *testing.T) {
p := testParser(`
(common) {
@@ -617,7 +577,7 @@ func TestSnippets(t *testing.T) {
}
for _, b := range blocks {
t.Log(b.Keys)
- t.Log(b.Tokens)
+ t.Log(b.Segments)
}
if len(blocks) != 1 {
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 {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
}
- if len(blocks[0].Tokens) != 2 {
- t.Fatalf("Server block should have tokens from import")
+ if len(blocks[0].Segments) != 2 {
+ 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)
}
- 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)
}
-
}
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
@@ -666,9 +625,9 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
}
for _, b := range blocks {
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
if line != "basicauth / import password" {
// Previously, it would be changed to:
@@ -701,7 +660,7 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
}
for _, b := range blocks {
t.Log(b.Keys)
- t.Log(b.Tokens)
+ t.Log(b.Segments)
}
if len(blocks) != 1 {
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 {
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")
}
- 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)
}
}
+
+func testParser(input string) parser {
+ return parser{Dispenser: newTestDispenser(input)}
+}
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go
index 6ecee26..2adb818 100644
--- a/caddyconfig/httpcaddyfile/addresses.go
+++ b/caddyconfig/httpcaddyfile/addresses.go
@@ -22,7 +22,7 @@ import (
"strconv"
"strings"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/mholt/certmagic"
)
@@ -73,8 +73,8 @@ import (
// repetition may be undesirable, so call consolidateAddrMappings() to map
// multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.)
-func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.ServerBlock) (map[string][]caddyfile.ServerBlock, error) {
- sbmap := make(map[string][]caddyfile.ServerBlock)
+func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) {
+ sbmap := make(map[string][]serverBlock)
for i, sblock := range originalServerBlocks {
// 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
// contents in cases where multiple keys really can be served together
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
// arguments to the 'bind' directive (although they will all have
// 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
// the contents (tokens) are of course the same
for addr, keys := range addrToKeys {
- sbmap[addr] = append(sbmap[addr], caddyfile.ServerBlock{
- Keys: keys,
- Tokens: sblock.Tokens,
+ sbmap[addr] = append(sbmap[addr], serverBlock{
+ block: caddyfile.ServerBlock{
+ 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
// association from multiple addresses to multiple server blocks; i.e. each element of
// 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
for addr, sblocks := range addrToServerBlocks {
// we start with knowing that at least this address
@@ -151,11 +154,12 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]ca
return sbaddrs
}
-func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBlock, key string) ([]string, error) {
- addr, err := standardizeAddress(key)
+func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) {
+ addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
+ addr = addr.Normalize()
lnPort := defaultPort
if addr.Port != "" {
@@ -168,11 +172,8 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBloc
// the bind directive specifies hosts, but is optional
var lnHosts []string
- for i, token := range sblock.Tokens["bind"] {
- if i == 0 {
- continue
- }
- lnHosts = append(lnHosts, token.Text)
+ for _, cfgVal := range sblock.pile["bind"] {
+ lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
}
if len(lnHosts) == 0 {
lnHosts = []string{""}
@@ -205,7 +206,53 @@ type Address struct {
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 {
if a.Host == "" && a.Port == "" {
return ""
@@ -235,16 +282,7 @@ func (a Address) String() string {
return s
}
-// VHost returns a sensible concatenation of Host:Port/Path from 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
+// Normalize returns a normalized version of a.
func (a Address) Normalize() Address {
path := a.Path
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.
-// Unlike String it doesn't add anything default (scheme, port, etc)
+// Key returns a string form of a, much like String() does, but this
+// method doesn't add anything default that wasn't in the original.
func (a Address) Key() string {
res := ""
if a.Scheme != "" {
@@ -276,11 +314,11 @@ func (a Address) Key() string {
if a.Host != "" {
res += a.Host
}
- if a.Port != "" {
- if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
- // insert port only if the original has its own explicit port
- res += ":" + a.Port
- }
+ // insert port only if the original has its own explicit port
+ if a.Port != "" &&
+ len(a.Original) >= len(res) &&
+ strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
+ res += ":" + a.Port
}
if a.Path != "" {
res += a.Path
@@ -288,63 +326,7 @@ func (a Address) Key() string {
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 (
defaultPort = "2015"
- caseSensitivePath = false
+ caseSensitivePath = false // TODO: Used?
)
diff --git a/caddyconfig/httpcaddyfile/addresses_test.go b/caddyconfig/httpcaddyfile/addresses_test.go
index 7e03d29..d6aa6f6 100644
--- a/caddyconfig/httpcaddyfile/addresses_test.go
+++ b/caddyconfig/httpcaddyfile/addresses_test.go
@@ -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
-import "testing"
+import (
+ "strings"
+ "testing"
+)
-func TestStandardizeAddress(t *testing.T) {
+func TestParseAddress(t *testing.T) {
for i, test := range []struct {
input string
scheme, host, port, path string
@@ -31,14 +20,15 @@ func TestStandardizeAddress(t *testing.T) {
{`[::1]`, "", "::1", "", "", false},
{`[::1]:1234`, "", "::1", "1234", "", false},
{`:`, "", "", "", "", false},
- {`localhost:http`, "http", "localhost", "80", "", false},
- {`localhost:https`, "https", "localhost", "443", "", false},
- {`:http`, "http", "", "80", "", false},
- {`:https`, "https", "", "443", "", false},
+ {`:http`, "", "", "", "", true},
+ {`:https`, "", "", "", "", true},
+ {`localhost:http`, "", "", "", "", true}, // using service name in port is verboten, as of Go 1.12.8
+ {`localhost:https`, "", "", "", "", true},
{`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
- {`http://localhost:443`, "", "", "", "", true}, // not conventional
- {`https://localhost:80`, "", "", "", "", true}, // not conventional
+ {`host:https/path`, "", "", "", "", true},
+ {`http://localhost:443`, "", "", "", "", true}, // not conventional
+ {`https://localhost:80`, "", "", "", "", true}, // not conventional
{`http://localhost`, "http", "localhost", "80", "", false},
{`https://localhost`, "https", "localhost", "443", "", 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},
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
{`host:80/path`, "", "host", "80", "/path", false},
- {`host:https/path`, "https", "host", "443", "/path", false},
{`/path`, "", "", "", "/path", false},
} {
- actual, err := standardizeAddress(test.input)
+ actual, err := ParseAddress(test.input)
if err != nil && !test.shouldErr {
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) {
for i, test := range []struct {
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)
+ }
+
+ }
+}
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 7e51e46..0fdfcd5 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -19,239 +19,237 @@ import (
"fmt"
"html"
"net/http"
+ "reflect"
- "github.com/caddyserver/caddy/v2/caddyconfig"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
- "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+ "github.com/caddyserver/caddy/caddyconfig"
+ "github.com/caddyserver/caddy/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
-func (st *ServerType) parseRoot(
- tkns []caddyfile.Token,
- matcherDefs map[string]map[string]json.RawMessage,
- warnings *[]caddyconfig.Warning,
-) ([]caddyhttp.Route, error) {
- 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}
- }
+func init() {
+ RegisterDirective("bind", parseBind)
+ RegisterDirective("root", parseRoot)
+ RegisterDirective("tls", parseTLS)
+ RegisterHandlerDirective("redir", parseRedir)
+}
- routes = append(routes, route)
+func parseBind(h Helper) ([]ConfigValue, error) {
+ var lnHosts []string
+ for h.Next() {
+ lnHosts = append(lnHosts, h.RemainingArgs()...)
}
-
- return routes, nil
+ return h.NewBindAddresses(lnHosts), nil
}
-func (st *ServerType) parseRedir(
- tkns []caddyfile.Token,
- matcherDefs map[string]map[string]json.RawMessage,
- warnings *[]caddyconfig.Warning,
-) ([]caddyhttp.Route, error) {
- var routes []caddyhttp.Route
+func parseRoot(h Helper) ([]ConfigValue, error) {
+ if !h.Next() {
+ return nil, h.ArgErr()
+ }
- matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
+ matcherSet, ok, err := h.MatcherToken()
if err != nil {
return nil, err
}
-
- for _, mst := range matchersAndTokens {
- var route caddyhttp.Route
-
- d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
-
- for d.Next() {
- if !d.NextArg() {
- return nil, d.ArgErr()
- }
- to := d.Val()
-
- var code string
- if d.NextArg() {
- code = d.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>
- <head>
- <title>Redirecting...</title>
- <script>window.location.replace("%s");</script>
- <meta http-equiv="refresh" content="0; URL='%s'">
- </head>
- <body>Redirecting to <a href="%s">%s</a>...</body>
-</html>
-`
- safeTo := html.EscapeString(to)
- 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)
+ if !ok {
+ // no matcher token; oops
+ h.Dispenser.Prev()
}
- return routes, nil
-}
-
-func (st *ServerType) parseTLSAutomationManager(d *caddyfile.Dispenser) (caddytls.ACMEManagerMaker, error) {
- var m caddytls.ACMEManagerMaker
-
- 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
- }
- }
+ if !h.NextArg() {
+ return nil, h.ArgErr()
+ }
+ root := h.Val()
+ if h.NextArg() {
+ return nil, h.ArgErr()
+ }
- // a naked tls directive is not allowed
- if len(firstLine) == 0 && !hasBlock {
- return m, d.ArgErr()
- }
+ 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}
}
- return m, nil
+ return h.NewVarsRoute(route), nil
}
-func (st *ServerType) parseTLSCerts(d *caddyfile.Dispenser) (map[string]caddytls.CertificateLoader, error) {
+func parseTLS(h Helper) ([]ConfigValue, error) {
+ var configVals []ConfigValue
+
+ cp := new(caddytls.ConnectionPolicy)
var fileLoader caddytls.FileLoader
var folderLoader caddytls.FolderLoader
-
- for d.Next() {
- // file loader
- firstLine := d.RemainingArgs()
- if len(firstLine) == 2 {
+ 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]
+ }
+ case 2:
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
Certificate: firstLine[0],
Key: firstLine[1],
- // TODO: tags, for enterprise module's certificate selection
+ // TODO: add tags, for enterprise module's certificate selection
})
+ default:
+ return nil, h.ArgErr()
}
- // 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
-}
+ var hasBlock bool
+ for h.NextBlock() {
+ hasBlock = true
-func (st *ServerType) parseTLSConnPolicy(d *caddyfile.Dispenser) (*caddytls.ConnectionPolicy, error) {
- cp := new(caddytls.ConnectionPolicy)
+ switch h.Val() {
- for d.Next() {
- for d.NextBlock() {
- switch d.Val() {
+ // connection policy
case "protocols":
- args := d.RemainingArgs()
+ args := h.RemainingArgs()
if len(args) == 0 {
- return nil, d.SyntaxErr("one or two protocols")
+ return nil, h.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])
+ 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, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
+ return nil, h.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())
+ 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, d.Val())
+ cp.CipherSuites = append(cp.CipherSuites, h.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())
+ 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, d.Val())
+ cp.Curves = append(cp.Curves, h.Val())
}
case "alpn":
- args := d.RemainingArgs()
+ args := h.RemainingArgs()
if len(args) == 0 {
- return nil, d.ArgErr()
+ 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
}
}
+
+ // a naked tls directive is not allowed
+ if len(firstLine) == 0 && !hasBlock {
+ return nil, h.ArgErr()
+ }
+ }
+
+ // 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>
+ <head>
+ <title>Redirecting...</title>
+ <script>window.location.replace("%s");</script>
+ <meta http-equiv="refresh" content="0; URL='%s'">
+ </head>
+ <body>Redirecting to <a href="%s">%s</a>...</body>
+</html>
+`
+ safeTo := html.EscapeString(to)
+ body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
}
- return cp, nil
+ return caddyhttp.StaticResponse{
+ StatusCode: caddyhttp.WeakString(code),
+ Headers: http.Header{"Location": []string{to}},
+ Body: body,
+ }, nil
}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
new file mode 100644
index 0000000..526ac87
--- /dev/null
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -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)
diff --git a/caddyconfig/httpcaddyfile/handlers.go b/caddyconfig/httpcaddyfile/handlers.go
index a90aa4a..9a29e97 100644
--- a/caddyconfig/httpcaddyfile/handlers.go
+++ b/caddyconfig/httpcaddyfile/handlers.go
@@ -17,7 +17,6 @@ package httpcaddyfile
import (
"encoding/json"
"fmt"
- "log"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
@@ -55,38 +54,3 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin
}
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
-}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index e5bf048..1c12ccd 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -17,17 +17,18 @@ package httpcaddyfile
import (
"encoding/json"
"fmt"
+ "log"
"reflect"
+ "sort"
"strconv"
"strings"
- "github.com/mholt/certmagic"
-
"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"
"github.com/caddyserver/caddy/v2/modules/caddytls"
+ "github.com/mholt/certmagic"
)
func init() {
@@ -38,24 +39,57 @@ func init() {
type ServerType struct {
}
-// ValidDirectives returns the list of known 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
-}
+// TODO: error on unrecognized directives
// Setup makes a config from the tokens.
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
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
- sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks)
+ sbmap, err := st.mapAddressToServerBlocks(serverBlocks)
if err != nil {
return nil, warnings, err
}
@@ -63,6 +97,22 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
// reduce
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
// blocks is basically a server definition
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)}
for _, p := range pairings {
for _, sblock := range p.serverBlocks {
- if tkns, ok := sblock.Tokens["tls"]; ok {
- // extract all unique hostnames from the server block
- // keys, then convert to a slice for use in the TLS app
- hostMap := make(map[string]struct{})
- for _, sblockKey := range sblock.Keys {
- addr, err := standardizeAddress(sblockKey)
+ // tls automation policies
+ if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
+ for _, mmVal := range mmVals {
+ mm := mmVal.Value.(caddytls.ManagerMaker)
+ sblockHosts, err := st.autoHTTPSHosts(sblock)
if err != nil {
- return nil, warnings, fmt.Errorf("parsing server block key: %v", err)
+ return nil, warnings, err
}
- hostMap[addr.Host] = struct{}{}
- }
- sblockHosts := make([]string, 0, len(hostMap))
- for host := range hostMap {
- sblockHosts = append(sblockHosts, host)
+ tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
+ Hosts: sblockHosts,
+ ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &warnings),
+ })
}
+ }
- // parse tokens to get ACME manager config
- acmeMgr, err := st.parseTLSAutomationManager(caddyfile.NewDispenser("Caddyfile", tkns))
- if err != nil {
- return nil, warnings, err
- }
-
- 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 {
+ // tls certificate loaders
+ if clVals, ok := sblock.pile["tls.certificate_loader"]; ok {
+ for _, clVal := range clVals {
+ loader := clVal.Value.(caddytls.CertificateLoader)
+ loaderName := caddy.GetModuleName(loader)
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!
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
hostMap := make(map[string]struct{})
for _, sblockKey := range sb.Keys {
- addr, err := standardizeAddress(sblockKey)
+ addr, err := ParseAddress(sblockKey)
if err != nil {
return nil, fmt.Errorf("parsing server block key: %v", err)
}
+ addr = addr.Normalize()
hostMap[addr.Host] = struct{}{}
}
@@ -167,121 +206,75 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
}
for _, sblock := range p.serverBlocks {
- matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
+ matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
if err != nil {
- return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Keys, err)
- }
-
- // extract matcher definitions
- d := caddyfile.NewDispenser("Caddyfile", sblock.Tokens["matcher"])
- matcherDefs, err := st.parseMatcherDefinitions(d)
- if err != nil {
- return nil, err
+ return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
}
+ // if there are user-defined variables, then siteVarSubroute will
+ // wrap the handlerSubroute; otherwise handlerSubroute will be the
+ // site's primary subroute.
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
- // built-in directives
+ // tls: connection policies and toggle auto HTTPS
- // 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...)
+ autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
+ if err != nil {
+ return nil, err
}
-
- // tls: off and conn policies
- if tkns, ok := sblock.Tokens["tls"]; ok {
- // get the hosts for this server block...
- hosts, err := st.hostsFromServerBlockKeys(sblock)
- if err != nil {
- return nil, err
+ if _, ok := sblock.pile["tls.off"]; ok {
+ // tls off: disable TLS (and automatic HTTPS) for server block's names
+ if srv.AutoHTTPS == nil {
+ srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
-
- // ...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))
+ srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
+ } else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
+ // tls connection policies
+ for _, cpVal := range cpVals {
+ cp := cpVal.Value.(*caddytls.ConnectionPolicy)
+ // only create if there is a non-empty policy
+ if !reflect.DeepEqual(cp, new(caddytls.ConnectionPolicy)) {
+ // make sure the policy covers all hostnames from the block
+ hosts, err := st.hostsFromServerBlockKeys(sblock.block)
if err != nil {
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{
- Handle: []json.RawMessage{
- caddyconfig.JSONModuleObject(handler, "handler", dir, warnings),
- },
- }
- if mst.matcherSet != nil {
- route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
+ // 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
}
- handlerSubroute.Routes = append(handlerSubroute.Routes, route)
+ srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
}
-
}
+ // TODO: consolidate equal conn policies
}
- // redir: static responses that redirect
- if tkns, ok := sblock.Tokens["redir"]; ok {
- routes, err := st.parseRedir(tkns, matcherDefs, warnings)
- if err != nil {
- return nil, err
+ // vars: special routes that will have to wrap the normal handlers
+ // so that these variables can be used across their matchers too
+ for _, cfgVal := range sblock.pile["var"] {
+ siteVarSubroute.Routes = append(siteVarSubroute.Routes, cfgVal.Value.(caddyhttp.Route))
+ }
+
+ // 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
@@ -298,7 +291,7 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
siteSubroute.Routes = append(
siteVarSubroute.Routes,
caddyhttp.Route{
- Handle: []json.RawMessage{
+ HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
},
},
@@ -308,8 +301,8 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
srv.Routes = append(srv.Routes, caddyhttp.Route{
- MatcherSets: matcherSetsEnc,
- Handle: []json.RawMessage{
+ MatcherSetsRaw: matcherSetsEnc,
+ HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
},
})
@@ -323,16 +316,32 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
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
// (same matchers, same Terminal and Group settings) for a
// cleaner overall output.
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
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].Group == routes[i+1].Group {
// 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:]...)
i--
}
@@ -340,53 +349,26 @@ func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
return routes
}
-func (st *ServerType) tokensToMatcherSets(
- tkns []caddyfile.Token,
- matcherDefs map[string]map[string]json.RawMessage,
- warnings *[]caddyconfig.Warning,
-) (map[string]matcherSetAndTokens, error) {
- m := make(map[string]matcherSetAndTokens)
-
- 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 {
- // found a matcher; save it, then splice it out
- // since we don't want to parse it again
- matcherToken = d.Token()
- tkns = d.Delete()
+// consolidateAutomationPolicies combines automation policies that are the same,
+// for a cleaner overall output.
+func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.AutomationPolicy {
+ for i := 0; i < len(aps); i++ {
+ for j := 0; j < len(aps); j++ {
+ if j == i {
+ continue
}
- d.RemainingArgs() // advance to end of line
- }
- for d.NextBlock() {
- // skip entire block including any nested blocks; all
- // we care about is accessing next directive occurrence
- for d.Nested() {
- d.NextBlock()
+ if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) {
+ aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...)
}
+ aps = append(aps[:j], aps[j+1:]...)
+ i--
+ break
}
- 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,
matcherDefs map[string]map[string]json.RawMessage,
warnings *[]caddyconfig.Warning,
@@ -424,10 +406,11 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
var matcherPairs []*hostPathPair
for _, key := range sblock.Keys {
- addr, err := standardizeAddress(key)
+ addr, err := ParseAddress(key)
if err != nil {
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
// server block; if none exists yet, create one
@@ -504,14 +487,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]
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
// the error to a warning and returns 0.
func tryInt(str string, warnings *[]caddyconfig.Warning) int {
@@ -535,7 +510,7 @@ type matcherSetAndTokens struct {
// served on those addresses.
type sbAddrAssociation struct {
addresses []string
- serverBlocks []caddyfile.ServerBlock
+ serverBlocks []serverBlock
}
// Interface guard
diff --git a/context.go b/context.go
index 17488e4..2fd84d5 100644
--- a/context.go
+++ b/context.go
@@ -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)
}
- 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 {
- 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
diff --git a/modules.go b/modules.go
index f1b4765..ad03adf 100644
--- a/modules.go
+++ b/modules.go
@@ -23,48 +23,76 @@ import (
"sync"
)
-// Module represents a Caddy module.
-type Module struct {
+// Module is a type that is used as a Caddy module.
+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
// must be unique and properly namespaced.
Name string
- // New returns a new, empty instance of
- // the module's type. The host module
- // which loads this module will likely
- // invoke methods on the returned value.
- // It must return a pointer; if not, it
- // is converted into one.
- New func() interface{}
+ // New returns a pointer to a new, empty
+ // instance of the module's type. The host
+ // module which instantiates this module will
+ // likely type-assert and invoke methods on
+ // the returned value. This function must not
+ // have any side-effects.
+ New func() Module
+}
+
+// Namespace returns the module's namespace (scope)
+// which is all but the last element of its name.
+// If there is no explicit namespace in the name,
+// the whole name is considered the namespace.
+func (mi ModuleInfo) Namespace() string {
+ lastDot := strings.LastIndex(mi.Name, ".")
+ if lastDot < 0 {
+ return mi.Name
+ }
+ return mi.Name[:lastDot]
}
// ID returns a module's ID, which is the
// last element of its name.
-func (m Module) ID() string {
- if m.Name == "" {
+func (mi ModuleInfo) ID() string {
+ if mi.Name == "" {
return ""
}
- parts := strings.Split(m.Name, ".")
+ parts := strings.Split(mi.Name, ".")
return parts[len(parts)-1]
}
-// Namespace returns the module's namespace (scope)
-// which is all but the last element of its name.
-func (m Module) Namespace() string {
- lastDot := strings.LastIndex(m.Name, ".")
- if lastDot < 0 {
- return ""
+func (mi ModuleInfo) String() string { return mi.Name }
+
+// RegisterModule registers a module by receiving a
+// plain/empty value of the module. For registration to
+// 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")
}
- return m.Name[:lastDot]
-}
-
-func (m Module) String() string { return m.Name }
-
-// RegisterModule registers a module. Modules must call
-// this function in the init phase of runtime.
-func RegisterModule(mod Module) error {
- if mod.Name == "caddy" {
- return fmt.Errorf("modules cannot be named 'caddy'")
+ 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()
@@ -77,18 +105,27 @@ func RegisterModule(mod Module) error {
return nil
}
-// GetModule returns a module by its full name.
-func GetModule(name string) (Module, error) {
+// GetModule returns module information from its full name.
+func GetModule(name string) (ModuleInfo, error) {
modulesMu.Lock()
defer modulesMu.Unlock()
-
m, ok := modules[name]
if !ok {
- return Module{}, fmt.Errorf("module not registered: %s", name)
+ return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
}
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.
// For example, a scope of "foo" returns modules named "foo.bar",
// "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
// will be sorted to keep it deterministic.
-func GetModules(scope string) []Module {
+func GetModules(scope string) []ModuleInfo {
modulesMu.Lock()
defer modulesMu.Unlock()
@@ -110,7 +147,7 @@ func GetModules(scope string) []Module {
scopeParts = []string{}
}
- var mods []Module
+ var mods []ModuleInfo
iterateModules:
for name, m := range modules {
modParts := strings.Split(name, ".")
@@ -223,6 +260,6 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
}
var (
- modules = make(map[string]Module)
+ modules = make(map[string]ModuleInfo)
modulesMu sync.Mutex
)
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 21c5b6d..b4b1ec6 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -37,10 +37,7 @@ import (
func init() {
weakrand.Seed(time.Now().UnixNano())
- err := caddy.RegisterModule(caddy.Module{
- Name: "http",
- New: func() interface{} { return new(App) },
- })
+ err := caddy.RegisterModule(App{})
if err != nil {
log.Fatal(err)
}
@@ -58,6 +55,14 @@ type App struct {
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.
func (app *App) Provision(ctx caddy.Context) error {
app.ctx = ctx
@@ -227,7 +232,7 @@ func (app *App) automaticHTTPS() error {
// find all qualifying domain names, de-duplicated
domainSet := make(map[string]struct{})
for _, route := range srv.Routes {
- for _, matcherSet := range route.matcherSets {
+ for _, matcherSet := range route.MatcherSets {
for _, m := range matcherSet {
if hm, ok := m.(*MatchHost); ok {
for _, d := range *hm {
@@ -331,13 +336,13 @@ func (app *App) automaticHTTPS() error {
redirTo += "{http.request.uri}"
redirRoutes = append(redirRoutes, Route{
- matcherSets: []MatcherSet{
+ MatcherSets: []MatcherSet{
{
MatchProtocol("http"),
MatchHost(domains),
},
},
- handlers: []MiddlewareHandler{
+ Handlers: []MiddlewareHandler{
StaticResponse{
StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
Headers: http.Header{
diff --git a/modules/caddyhttp/caddylog/log.go b/modules/caddyhttp/caddylog/log.go
index 902f60f..3f636d1 100644
--- a/modules/caddyhttp/caddylog/log.go
+++ b/modules/caddyhttp/caddylog/log.go
@@ -24,10 +24,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.log",
- New: func() interface{} { return new(Log) },
- })
+ caddy.RegisterModule(Log{})
}
// Log implements a simple logging middleware.
@@ -36,6 +33,14 @@ type Log struct {
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 {
start := time.Now()
diff --git a/modules/caddyhttp/encode/brotli/brotli.go b/modules/caddyhttp/encode/brotli/brotli.go
index 0a9f871..cf055aa 100644
--- a/modules/caddyhttp/encode/brotli/brotli.go
+++ b/modules/caddyhttp/encode/brotli/brotli.go
@@ -19,16 +19,13 @@ import (
"strconv"
"github.com/andybalholm/brotli"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.encoders.brotli",
- New: func() interface{} { return new(Brotli) },
- })
+ caddy.RegisterModule(Brotli{})
}
// Brotli can create brotli encoders. Note that brotli
@@ -37,6 +34,14 @@ type Brotli struct {
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.
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
diff --git a/modules/caddyhttp/encode/caddyfile.go b/modules/caddyhttp/encode/caddyfile.go
index 5aca6ac..5762bd3 100644
--- a/modules/caddyhttp/encode/caddyfile.go
+++ b/modules/caddyhttp/encode/caddyfile.go
@@ -22,8 +22,25 @@ import (
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"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:
//
// encode [<matcher>] <formats...> {
@@ -78,8 +95,5 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
-// Bucket returns the HTTP Caddyfile handler bucket number.
-func (enc Encode) Bucket() int { return 3 }
-
// Interface guard
-var _ httpcaddyfile.HandlerDirective = (*Encode)(nil)
+var _ caddyfile.Unmarshaler = (*Encode)(nil)
diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go
index 4e5f743..723b988 100644
--- a/modules/caddyhttp/encode/encode.go
+++ b/modules/caddyhttp/encode/encode.go
@@ -35,10 +35,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.encode",
- New: func() interface{} { return new(Encode) },
- })
+ caddy.RegisterModule(Encode{})
}
// 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...
}
+// 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.
func (enc *Encode) Provision(ctx caddy.Context) error {
for modName, rawMsg := range enc.EncodingsRaw {
diff --git a/modules/caddyhttp/encode/gzip/gzip.go b/modules/caddyhttp/encode/gzip/gzip.go
index 87e8816..d6d67f7 100644
--- a/modules/caddyhttp/encode/gzip/gzip.go
+++ b/modules/caddyhttp/encode/gzip/gzip.go
@@ -20,16 +20,13 @@ import (
"fmt"
"strconv"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.encoders.gzip",
- New: func() interface{} { return new(Gzip) },
- })
+ caddy.RegisterModule(Gzip{})
}
// Gzip can create gzip encoders.
@@ -37,6 +34,14 @@ type Gzip struct {
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.
func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
diff --git a/modules/caddyhttp/encode/zstd/zstd.go b/modules/caddyhttp/encode/zstd/zstd.go
index 3622628..f2b4e85 100644
--- a/modules/caddyhttp/encode/zstd/zstd.go
+++ b/modules/caddyhttp/encode/zstd/zstd.go
@@ -15,22 +15,27 @@
package caddyzstd
import (
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
"github.com/klauspost/compress/zstd"
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.encoders.zstd",
- New: func() interface{} { return new(Zstd) },
- })
+ caddy.RegisterModule(Zstd{})
}
// Zstd can create Zstandard encoders.
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.
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go
index 7d9ddd9..6fa94e7 100644
--- a/modules/caddyhttp/fileserver/caddyfile.go
+++ b/modules/caddyhttp/fileserver/caddyfile.go
@@ -15,59 +15,58 @@
package fileserver
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/modules/caddyhttp"
)
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
-//
-// file_server [<matcher>] [browse] {
-// hide <files...>
-// index <files...>
-// browse [<template_file>]
-// root <path>
-// }
-//
-// If browse is given on the first line, it can't be used in the block also.
-// 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()
+func init() {
+ httpcaddyfile.RegisterHandlerDirective("file_server", parseCaddyfile)
+ httpcaddyfile.RegisterDirective("try_files", parseTryFiles)
+}
+
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ var fsrv FileServer
+
+ for h.Next() {
+ args := h.RemainingArgs()
switch len(args) {
case 0:
case 1:
if args[0] != "browse" {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
fsrv.Browse = new(Browse)
default:
- return d.ArgErr()
+ return nil, h.ArgErr()
}
- for d.NextBlock() {
- switch d.Val() {
+ for h.NextBlock() {
+ switch h.Val() {
case "hide":
- fsrv.Hide = d.RemainingArgs()
+ fsrv.Hide = h.RemainingArgs()
if len(fsrv.Hide) == 0 {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
case "index":
- fsrv.IndexNames = d.RemainingArgs()
+ fsrv.IndexNames = h.RemainingArgs()
if len(fsrv.Hide) == 0 {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
case "root":
- if !d.Args(&fsrv.Root) {
- return d.ArgErr()
+ if !h.Args(&fsrv.Root) {
+ return nil, h.ArgErr()
}
case "browse":
if fsrv.Browse != nil {
- return d.Err("browsing is already configured")
+ return nil, h.Err("browsing is already configured")
}
fsrv.Browse = new(Browse)
- d.Args(&fsrv.Browse.TemplateFile)
+ h.Args(&fsrv.Browse.TemplateFile)
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}"
}
- return nil
+ return &fsrv, nil
}
-// Bucket returns the HTTP Caddyfile handler bucket number.
-func (fsrv FileServer) Bucket() int { return 7 }
+func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
+ if !h.Next() {
+ return nil, h.ArgErr()
+ }
+
+ try := h.RemainingArgs()
+ if len(try) == 0 {
+ return nil, h.ArgErr()
+ }
+
+ handler := rewrite.Rewrite{
+ URI: "{http.matchers.file.relative}{http.request.uri.query}",
+ }
-// Interface guard
-var _ httpcaddyfile.HandlerDirective = (*FileServer)(nil)
+ matcherSet := map[string]json.RawMessage{
+ "file": h.JSON(MatchFile{
+ Root: "{http.var.root}",
+ TryFiles: try,
+ }, nil),
+ }
+
+ return h.NewRoute(matcherSet, handler), nil
+}
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index eca0e8f..b091250 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -20,16 +20,13 @@ import (
"os"
"time"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.matchers.file",
- New: func() interface{} { return new(MatchFile) },
- })
+ caddy.RegisterModule(MatchFile{})
}
// MatchFile is an HTTP request matcher that can match
@@ -52,12 +49,20 @@ type MatchFile struct {
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:
//
// file {
// root <path>
// 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 {
@@ -82,6 +87,9 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
}
}
}
+ if m.Root == "" {
+ m.Root = "{http.var.root}"
+ }
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) {
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,
// assume URL path
diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go
index 1b542cf..cdac453 100644
--- a/modules/caddyhttp/fileserver/staticfiles.go
+++ b/modules/caddyhttp/fileserver/staticfiles.go
@@ -36,10 +36,7 @@ import (
func init() {
weakrand.Seed(time.Now().UnixNano())
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.file_server",
- New: func() interface{} { return new(FileServer) },
- })
+ caddy.RegisterModule(FileServer{})
}
// FileServer implements a static file server responder for Caddy.
@@ -50,6 +47,14 @@ type FileServer struct {
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.
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
if fsrv.IndexNames == nil {
diff --git a/modules/caddyhttp/headers/caddyfile.go b/modules/caddyhttp/headers/caddyfile.go
index 8d320e5..5eaf064 100644
--- a/modules/caddyhttp/headers/caddyfile.go
+++ b/modules/caddyhttp/headers/caddyfile.go
@@ -18,11 +18,15 @@ import (
"net/http"
"strings"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"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>] {
// [+][<field>] [<value>]
@@ -31,62 +35,57 @@ import (
//
// 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.
-func (h *Headers) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- for d.Next() {
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ hdr := new(Headers)
+ for h.Next() {
// first see if headers are in the initial line
var hasArgs bool
- if d.NextArg() {
+ if h.NextArg() {
hasArgs = true
- field := d.Val()
- d.NextArg()
- value := d.Val()
- h.processCaddyfileLine(field, value)
+ field := h.Val()
+ h.NextArg()
+ value := h.Val()
+ processCaddyfileLine(hdr, field, value)
}
// if not, they should be in a block
- for d.NextBlock() {
+ for h.NextBlock() {
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
- if d.NextArg() {
- value = d.Val()
+ if h.NextArg() {
+ 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 h.Response == nil {
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ if hdr.Response == nil {
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
}
- if h.Response.Add == nil {
- h.Response.Add = make(http.Header)
+ if hdr.Response.Add == nil {
+ 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, "-") {
- if h.Response == nil {
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ if hdr.Response == nil {
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
}
- h.Response.Delete = append(h.Response.Delete, field[1:])
- h.Response.Deferred = true
+ hdr.Response.Delete = append(hdr.Response.Delete, field[1:])
+ hdr.Response.Deferred = true
} else {
- if h.Response == nil {
- h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
+ if hdr.Response == nil {
+ hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
}
- if h.Response.Set == nil {
- h.Response.Set = make(http.Header)
+ if hdr.Response.Set == nil {
+ 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)
diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go
index 8f4976a..e740004 100644
--- a/modules/caddyhttp/headers/headers.go
+++ b/modules/caddyhttp/headers/headers.go
@@ -23,10 +23,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.headers",
- New: func() interface{} { return new(Headers) },
- })
+ caddy.RegisterModule(Headers{})
}
// Headers is a middleware which can mutate HTTP headers.
@@ -35,6 +32,14 @@ type Headers struct {
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
// perform on HTTP headers.
type HeaderOps struct {
diff --git a/modules/caddyhttp/markdown/markdown.go b/modules/caddyhttp/markdown/markdown.go
index 3ba4d02..122aad6 100644
--- a/modules/caddyhttp/markdown/markdown.go
+++ b/modules/caddyhttp/markdown/markdown.go
@@ -28,16 +28,21 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.markdown",
- New: func() interface{} { return new(Markdown) },
- })
+ caddy.RegisterModule(Markdown{})
}
// Markdown is a middleware for rendering a Markdown response body.
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 {
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go
index 72b5476..0dac151 100644
--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -80,50 +80,25 @@ type (
)
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",
- New: func() interface{} { 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) },
- })
+ New: func() caddy.Module { return new(MatchHost) },
+ }
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@@ -165,6 +140,14 @@ outer:
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.
func (m MatchPath) Match(r *http.Request) bool {
for _, matchPath := range m {
@@ -186,19 +169,39 @@ func (m MatchPath) Match(r *http.Request) bool {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- *m = d.RemainingArgs()
+ for d.Next() {
+ *m = d.RemainingArgs()
+ }
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.
func (m MatchPathRE) Match(r *http.Request) bool {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
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.
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- *m = d.RemainingArgs()
+ for d.Next() {
+ *m = d.RemainingArgs()
+ }
return nil
}
@@ -212,6 +215,14 @@ func (m MatchMethod) Match(r *http.Request) bool {
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.
func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
@@ -237,6 +248,14 @@ func (m MatchQuery) Match(r *http.Request) bool {
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.
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
@@ -270,6 +289,14 @@ func (m MatchHeader) Match(r *http.Request) bool {
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.
func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if *m == nil {
@@ -319,6 +346,14 @@ func (m MatchHeaderRE) Validate() error {
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.
func (m MatchProtocol) Match(r *http.Request) bool {
switch string(m) {
@@ -334,14 +369,24 @@ func (m MatchProtocol) Match(r *http.Request) bool {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- var proto string
- if !d.Args(&proto) {
- return d.Err("expected exactly one protocol")
+ for d.Next() {
+ var proto string
+ if !d.Args(&proto) {
+ return d.Err("expected exactly one protocol")
+ }
+ *m = MatchProtocol(proto)
}
- *m = MatchProtocol(proto)
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.
// This is done because we cannot embed the map directly into
// 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)
}
+// 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.
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- m.Ranges = d.RemainingArgs()
+ for d.Next() {
+ m.Ranges = d.RemainingArgs()
+ }
return nil
}
@@ -442,6 +497,14 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
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.
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
input := string(m)
@@ -513,8 +576,17 @@ func (mre *MatchRegexp) Match(input string, repl caddy.Replacer, scope string) b
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- if !d.Args(&mre.Name, &mre.Pattern) {
- return fmt.Errorf("missing arguments")
+ for d.Next() {
+ 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
}
diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go
index 3763cfe..9b16250 100644
--- a/modules/caddyhttp/requestbody/requestbody.go
+++ b/modules/caddyhttp/requestbody/requestbody.go
@@ -22,10 +22,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.request_body",
- New: func() interface{} { return new(RequestBody) },
- })
+ caddy.RegisterModule(RequestBody{})
}
// RequestBody is a middleware for manipulating the request body.
@@ -33,6 +30,14 @@ type RequestBody struct {
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 {
if r.Body == nil {
return next.ServeHTTP(w, r)
diff --git a/modules/caddyhttp/reverseproxy/module.go b/modules/caddyhttp/reverseproxy/module.go
index ff5786c..21aca1d 100755
--- a/modules/caddyhttp/reverseproxy/module.go
+++ b/modules/caddyhttp/reverseproxy/module.go
@@ -15,39 +15,39 @@
package reverseproxy
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/caddyconfig/httpcaddyfile"
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
-// Register caddy module.
func init() {
- caddy.RegisterModule(caddy.Module{
+ caddy.RegisterModule(new(LoadBalanced))
+ httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile) // TODO: "proxy"?
+}
+
+// CaddyModule returns the Caddy module information.
+func (*LoadBalanced) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
Name: "http.handlers.reverse_proxy",
- New: func() interface{} { return new(LoadBalanced) },
- })
+ New: func() caddy.Module { return new(LoadBalanced) },
+ }
}
-// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
//
// proxy [<matcher>] <to>
//
// TODO: This needs to be finished. It definitely needs to be able to open a block...
-func (lb *LoadBalanced) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- for d.Next() {
- allTo := d.RemainingArgs()
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ lb := new(LoadBalanced)
+ for h.Next() {
+ allTo := h.RemainingArgs()
if len(allTo) == 0 {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
for _, to := range allTo {
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)
diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go
index a067fe1..a977a72 100644
--- a/modules/caddyhttp/rewrite/caddyfile.go
+++ b/modules/caddyhttp/rewrite/caddyfile.go
@@ -15,24 +15,23 @@
package rewrite
import (
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"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>
//
// The <to> parameter becomes the new URI.
-func (rewr *Rewrite) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- for d.Next() {
- rewr.URI = d.Val()
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ var rewr Rewrite
+ 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)
diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go
index ac113ff..f434a38 100644
--- a/modules/caddyhttp/rewrite/rewrite.go
+++ b/modules/caddyhttp/rewrite/rewrite.go
@@ -24,10 +24,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.rewrite",
- New: func() interface{} { return new(Rewrite) },
- })
+ caddy.RegisterModule(Rewrite{})
}
// Rewrite is a middleware which can rewrite HTTP requests.
@@ -37,6 +34,14 @@ type Rewrite struct {
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 {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
var rehandleNeeded bool
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go
index ffa7ce7..1efbad6 100644
--- a/modules/caddyhttp/routes.go
+++ b/modules/caddyhttp/routes.go
@@ -26,33 +26,34 @@ import (
// middlewares, and a responder for handling HTTP
// requests.
type Route struct {
- Group string `json:"group,omitempty"`
- MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
- Handle []json.RawMessage `json:"handle,omitempty"`
- Terminal bool `json:"terminal,omitempty"`
+ Group string `json:"group,omitempty"`
+ MatcherSetsRaw []map[string]json.RawMessage `json:"match,omitempty"`
+ HandlersRaw []json.RawMessage `json:"handle,omitempty"`
+ Terminal bool `json:"terminal,omitempty"`
// decoded values
- matcherSets []MatcherSet
- handlers []MiddlewareHandler
+ MatcherSets []MatcherSet `json:"-"`
+ Handlers []MiddlewareHandler `json:"-"`
}
// Empty returns true if the route has all zero/default values.
func (r Route) Empty() bool {
- return len(r.MatcherSets) == 0 &&
- len(r.Handle) == 0 &&
- len(r.handlers) == 0 &&
+ return len(r.MatcherSetsRaw) == 0 &&
+ len(r.MatcherSets) == 0 &&
+ len(r.HandlersRaw) == 0 &&
+ len(r.Handlers) == 0 &&
!r.Terminal &&
r.Group == ""
}
func (r Route) anyMatcherSetMatches(req *http.Request) bool {
- for _, ms := range r.matcherSets {
+ for _, ms := range r.MatcherSets {
if ms.Match(req) {
return true
}
}
// if no matchers, always match
- return len(r.matcherSets) == 0
+ return len(r.MatcherSets) == 0
}
// MatcherSet is a set of matchers which
@@ -79,7 +80,7 @@ type RouteList []Route
func (routes RouteList) Provision(ctx caddy.Context) error {
for i, route := range routes {
// matchers
- for _, matcherSet := range route.MatcherSets {
+ for _, matcherSet := range route.MatcherSetsRaw {
var matchers MatcherSet
for modName, rawMsg := range matcherSet {
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))
}
- 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
- for j, rawMsg := range route.Handle {
+ for j, rawMsg := range route.HandlersRaw {
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
if err != nil {
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
}
@@ -135,7 +136,7 @@ func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
}
// 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
// of our current stack frame so that the
// reference to this mh isn't overwritten
diff --git a/modules/caddyhttp/staticerror.go b/modules/caddyhttp/staticerror.go
index 1834cf7..3a45366 100644
--- a/modules/caddyhttp/staticerror.go
+++ b/modules/caddyhttp/staticerror.go
@@ -23,10 +23,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.error",
- New: func() interface{} { return new(StaticError) },
- })
+ caddy.RegisterModule(StaticError{})
}
// StaticError implements a simple handler that returns an error.
@@ -35,6 +32,14 @@ type StaticError struct {
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 {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go
index cafee35..942459b 100644
--- a/modules/caddyhttp/staticresp.go
+++ b/modules/caddyhttp/staticresp.go
@@ -24,10 +24,8 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.static_response",
- New: func() interface{} { return new(StaticResponse) },
- })
+ caddy.RegisterModule(StaticResponse{})
+ // TODO: Caddyfile directive
}
// StaticResponse implements a simple responder for static responses.
@@ -38,6 +36,14 @@ type StaticResponse struct {
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:
//
// static_response [<matcher>] <status> {
@@ -71,9 +77,6 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
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 {
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go
index 9172146..3b0d718 100644
--- a/modules/caddyhttp/subroute.go
+++ b/modules/caddyhttp/subroute.go
@@ -22,10 +22,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.subroute",
- New: func() interface{} { return new(Subroute) },
- })
+ caddy.RegisterModule(Subroute{})
}
// Subroute implements a handler that compiles and executes routes.
@@ -37,6 +34,14 @@ type Subroute struct {
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.
func (sr *Subroute) Provision(ctx caddy.Context) error {
if sr.Routes != nil {
diff --git a/modules/caddyhttp/templates/caddyfile.go b/modules/caddyhttp/templates/caddyfile.go
index d27b8e3..d948da0 100644
--- a/modules/caddyhttp/templates/caddyfile.go
+++ b/modules/caddyhttp/templates/caddyfile.go
@@ -15,11 +15,15 @@
package templates
import (
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"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>] {
// mime <types...>
@@ -27,23 +31,24 @@ import (
// root <path>
// }
//
-func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
- for d.Next() {
- for d.NextBlock() {
- switch d.Val() {
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ t := new(Templates)
+ for h.Next() {
+ for h.NextBlock() {
+ switch h.Val() {
case "mime":
- t.MIMETypes = d.RemainingArgs()
+ t.MIMETypes = h.RemainingArgs()
if len(t.MIMETypes) == 0 {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
case "between":
- t.Delimiters = d.RemainingArgs()
+ t.Delimiters = h.RemainingArgs()
if len(t.Delimiters) != 2 {
- return d.ArgErr()
+ return nil, h.ArgErr()
}
case "root":
- if !d.Args(&t.IncludeRoot) {
- return d.ArgErr()
+ if !h.Args(&t.IncludeRoot) {
+ return nil, h.ArgErr()
}
}
}
@@ -53,11 +58,5 @@ func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
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)
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index 442e177..1cd347c 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -27,10 +27,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.templates",
- New: func() interface{} { return new(Templates) },
- })
+ caddy.RegisterModule(Templates{})
}
// Templates is a middleware which execute response bodies as templates.
@@ -40,6 +37,14 @@ type Templates struct {
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.
func (t *Templates) Provision(ctx caddy.Context) error {
if t.MIMETypes == nil {
diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go
index f74556a..bbd4568 100644
--- a/modules/caddyhttp/vars.go
+++ b/modules/caddyhttp/vars.go
@@ -21,20 +21,22 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "http.handlers.vars",
- New: func() interface{} { return new(VarsMiddleware) },
- })
- caddy.RegisterModule(caddy.Module{
- Name: "http.matchers.vars",
- New: func() interface{} { return new(VarsMiddleware) },
- })
+ caddy.RegisterModule(VarsMiddleware{})
+ caddy.RegisterModule(VarsMatcher{})
}
// VarsMiddleware is an HTTP middleware which sets variables
// in the context, mainly for use by placeholders.
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 {
vars := r.Context().Value(VarCtxKey).(map[string]interface{})
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.
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.
func (m VarsMatcher) Match(r *http.Request) bool {
vars := r.Context().Value(VarCtxKey).(map[string]string)
diff --git a/modules/caddytls/acmemanager.go b/modules/caddytls/acmemanager.go
index 578cdb3..36f1c21 100644
--- a/modules/caddytls/acmemanager.go
+++ b/modules/caddytls/acmemanager.go
@@ -28,10 +28,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "tls.management.acme",
- New: func() interface{} { return new(ACMEManagerMaker) },
- })
+ caddy.RegisterModule(ACMEManagerMaker{})
}
// ACMEManagerMaker makes an ACME manager
@@ -57,9 +54,17 @@ type ACMEManagerMaker struct {
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.
-func (m *ACMEManagerMaker) newManager(interactive bool) (certmagic.Manager, error) {
+func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error) {
return nil, nil
}
@@ -203,4 +208,4 @@ func onDemandAskRequest(ask string, name string) error {
}
// Interface guard
-var _ managerMaker = (*ACMEManagerMaker)(nil)
+var _ ManagerMaker = (*ACMEManagerMaker)(nil)
diff --git a/modules/caddytls/fileloader.go b/modules/caddytls/fileloader.go
index 7a0d14d..b2cc132 100644
--- a/modules/caddytls/fileloader.go
+++ b/modules/caddytls/fileloader.go
@@ -23,15 +23,20 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "tls.certificates.load_files",
- New: func() interface{} { return FileLoader{} },
- })
+ caddy.RegisterModule(FileLoader{})
}
// FileLoader loads certificates and their associated keys from disk.
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
// encoding format so that they can be loaded from disk.
type CertKeyFilePair struct {
diff --git a/modules/caddytls/folderloader.go b/modules/caddytls/folderloader.go
index ae7f056..da1dff0 100644
--- a/modules/caddytls/folderloader.go
+++ b/modules/caddytls/folderloader.go
@@ -28,10 +28,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "tls.certificates.load_folders",
- New: func() interface{} { return FolderLoader{} },
- })
+ caddy.RegisterModule(FolderLoader{})
}
// FolderLoader loads certificates and their associated keys from disk
@@ -39,6 +36,14 @@ func init() {
// files which contain both a certificate and a key.
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
// listed in fl from all files ending with .pem. This method of loading
// certificates expects the certificate and key to be bundled into the
diff --git a/modules/caddytls/matchers.go b/modules/caddytls/matchers.go
index ee146d4..47fb296 100644
--- a/modules/caddytls/matchers.go
+++ b/modules/caddytls/matchers.go
@@ -20,14 +20,19 @@ import (
"github.com/caddyserver/caddy/v2"
)
+func init() {
+ caddy.RegisterModule(MatchServerName{})
+}
+
// MatchServerName matches based on SNI.
type MatchServerName []string
-func init() {
- caddy.RegisterModule(caddy.Module{
+// CaddyModule returns the Caddy module information.
+func (MatchServerName) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
Name: "tls.handshake_match.sni",
- New: func() interface{} { return MatchServerName{} },
- })
+ New: func() caddy.Module { return new(MatchServerName) },
+ }
}
// Match matches hello based on SNI.
diff --git a/modules/caddytls/standardstek/stek.go b/modules/caddytls/standardstek/stek.go
index 6a4b1c8..6d10c76 100644
--- a/modules/caddytls/standardstek/stek.go
+++ b/modules/caddytls/standardstek/stek.go
@@ -24,10 +24,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "tls.stek.standard",
- New: func() interface{} { return new(standardSTEKProvider) },
- })
+ caddy.RegisterModule(standardSTEKProvider{})
}
type standardSTEKProvider struct {
@@ -35,6 +32,14 @@ type standardSTEKProvider struct {
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.
func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) {
// keep a reference to the config; we'll need it when rotating keys
diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go
index ec16995..88b7790 100644
--- a/modules/caddytls/tls.go
+++ b/modules/caddytls/tls.go
@@ -30,10 +30,7 @@ import (
)
func init() {
- caddy.RegisterModule(caddy.Module{
- Name: "tls",
- New: func() interface{} { return new(TLS) },
- })
+ caddy.RegisterModule(TLS{})
// opt-in TLS 1.3 for Go1.12
// TODO: remove this line when Go1.13 is released.
@@ -53,6 +50,14 @@ type TLS struct {
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.
func (t *TLS) Provision(ctx caddy.Context) error {
t.ctx = ctx
@@ -73,7 +78,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
if err != nil {
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?
}
@@ -237,7 +242,7 @@ type AutomationPolicy struct {
Hosts []string `json:"hosts,omitempty"`
ManagementRaw json.RawMessage `json:"management,omitempty"`
- Management managerMaker `json:"-"`
+ Management ManagerMaker `json:"-"`
}
// 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{
- NewManager: ap.Management.newManager,
+ NewManager: ap.Management.NewManager,
}
}
@@ -290,9 +295,9 @@ type RateLimit struct {
Burst int `json:"burst,omitempty"`
}
-// managerMaker makes a certificate manager.
-type managerMaker interface {
- newManager(interactive bool) (certmagic.Manager, error)
+// ManagerMaker makes a certificate manager.
+type ManagerMaker interface {
+ NewManager(interactive bool) (certmagic.Manager, error)
}
// These perpetual values are used for on-demand TLS.
diff --git a/modules_test.go b/modules_test.go
index ecba21f..ef7edf7 100644
--- a/modules_test.go
+++ b/modules_test.go
@@ -21,7 +21,7 @@ import (
func TestGetModules(t *testing.T) {
modulesMu.Lock()
- modules = map[string]Module{
+ modules = map[string]ModuleInfo{
"a": {Name: "a"},
"a.b": {Name: "a.b"},
"a.b.c": {Name: "a.b.c"},
@@ -38,11 +38,11 @@ func TestGetModules(t *testing.T) {
for i, tc := range []struct {
input string
- expect []Module
+ expect []ModuleInfo
}{
{
input: "",
- expect: []Module{
+ expect: []ModuleInfo{
{Name: "a"},
{Name: "b"},
{Name: "c"},
@@ -50,7 +50,7 @@ func TestGetModules(t *testing.T) {
},
{
input: "a",
- expect: []Module{
+ expect: []ModuleInfo{
{Name: "a.b"},
{Name: "a.c"},
{Name: "a.d"},
@@ -58,7 +58,7 @@ func TestGetModules(t *testing.T) {
},
{
input: "a.b",
- expect: []Module{
+ expect: []ModuleInfo{
{Name: "a.b.c"},
{Name: "a.b.cd"},
},
@@ -68,7 +68,7 @@ func TestGetModules(t *testing.T) {
},
{
input: "b",
- expect: []Module{
+ expect: []ModuleInfo{
{Name: "b.a"},
{Name: "b.b"},
},
diff --git a/storage.go b/storage.go
index 09336e9..f695a49 100644
--- a/storage.go
+++ b/storage.go
@@ -23,10 +23,7 @@ import (
)
func init() {
- RegisterModule(Module{
- Name: "caddy.storage.file_system",
- New: func() interface{} { return new(fileStorage) },
- })
+ RegisterModule(fileStorage{})
}
// StorageConverter is a type that can convert itself
@@ -43,6 +40,14 @@ type fileStorage struct {
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) {
return &certmagic.FileStorage{Path: s.Root}, nil
}