mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
Refactor Caddyfile adapter and module registration
Use piles from which to draw config values. Module values can return their name, so now we can do two-way mapping from value to name and name to value; whereas before we could only map name to value. This was problematic with the Caddyfile adapter since it receives values and needs to know the name to put in the config.
This commit is contained in:
parent
c4159ef76d
commit
c9980fd367
46 changed files with 1512 additions and 1105 deletions
11
admin.go
11
admin.go
|
@ -95,9 +95,9 @@ func StartAdmin(initialConfigJSON []byte) error {
|
||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
///// END PPROF STUFF //////
|
///// END PPROF STUFF //////
|
||||||
|
|
||||||
for _, m := range GetModules("admin") {
|
for _, m := range GetModules("admin.routers") {
|
||||||
routes := m.New().([]AdminRoute)
|
adminrtr := m.New().(AdminRouter)
|
||||||
for _, route := range routes {
|
for _, route := range adminrtr.Routes() {
|
||||||
mux.Handle(route.Pattern, route)
|
mux.Handle(route.Pattern, route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,6 +146,11 @@ func StopAdmin() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminRouter is a type which can return routes for the admin API.
|
||||||
|
type AdminRouter interface {
|
||||||
|
Routes() []AdminRoute
|
||||||
|
}
|
||||||
|
|
||||||
// AdminRoute represents a route for the admin endpoint.
|
// AdminRoute represents a route for the admin endpoint.
|
||||||
type AdminRoute struct {
|
type AdminRoute struct {
|
||||||
http.Handler
|
http.Handler
|
||||||
|
|
|
@ -37,14 +37,12 @@ func (a Adapter) Adapt(body []byte, options map[string]string) ([]byte, []caddyc
|
||||||
options = make(map[string]string)
|
options = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
directives := a.ServerType.ValidDirectives()
|
|
||||||
|
|
||||||
filename := options["filename"]
|
filename := options["filename"]
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
filename = "Caddyfile"
|
filename = "Caddyfile"
|
||||||
}
|
}
|
||||||
|
|
||||||
serverBlocks, err := Parse(filename, bytes.NewReader(body), directives)
|
serverBlocks, err := Parse(filename, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -77,10 +75,6 @@ type Unmarshaler interface {
|
||||||
|
|
||||||
// ServerType is a type that can evaluate a Caddyfile and set up a caddy config.
|
// ServerType is a type that can evaluate a Caddyfile and set up a caddy config.
|
||||||
type ServerType interface {
|
type ServerType interface {
|
||||||
// ValidDirectives returns a list of the
|
|
||||||
// server type's recognized directives.
|
|
||||||
ValidDirectives() []string
|
|
||||||
|
|
||||||
// Setup takes the server blocks which
|
// Setup takes the server blocks which
|
||||||
// contain tokens, as well as options
|
// contain tokens, as well as options
|
||||||
// (e.g. CLI flags) and creates a Caddy
|
// (e.g. CLI flags) and creates a Caddy
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Dispenser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDispenser returns a Dispenser filled with the given tokens.
|
// NewDispenser returns a Dispenser filled with the given tokens.
|
||||||
|
// TODO: Get rid of the filename argument; it seems pointless here
|
||||||
func NewDispenser(filename string, tokens []Token) *Dispenser {
|
func NewDispenser(filename string, tokens []Token) *Dispenser {
|
||||||
return &Dispenser{
|
return &Dispenser{
|
||||||
filename: filename,
|
filename: filename,
|
||||||
|
@ -51,15 +52,15 @@ func (d *Dispenser) Next() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prev moves to the previous token. It does the inverse
|
// Prev moves to the previous token. It does the inverse
|
||||||
// of Next(). Generally, this should only be used in
|
// of Next(), except this function may decrement the cursor
|
||||||
// special cases such as deleting a token from the slice
|
// to -1 so that the next call to Next() points to the
|
||||||
// that d is iterating. In that case, without using Prev(),
|
// first token; this allows dispensing to "start over". This
|
||||||
// the dispenser would be pointing at the wrong token since
|
// method returns true if the cursor ends up pointing to a
|
||||||
// deleting a token implicitly advances the cursor.
|
// valid token.
|
||||||
func (d *Dispenser) Prev() bool {
|
func (d *Dispenser) Prev() bool {
|
||||||
if d.cursor > 0 {
|
if d.cursor > -1 {
|
||||||
d.cursor--
|
d.cursor--
|
||||||
return true
|
return d.cursor > -1
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -223,8 +224,7 @@ func (d *Dispenser) RemainingArgs() []string {
|
||||||
// "directive" whether that be to the end of the line or
|
// "directive" whether that be to the end of the line or
|
||||||
// the end of a block that starts at the end of the line.
|
// the end of a block that starts at the end of the line.
|
||||||
func (d *Dispenser) NewFromNextTokens() *Dispenser {
|
func (d *Dispenser) NewFromNextTokens() *Dispenser {
|
||||||
var tkns []Token
|
tkns := []Token{d.Token()}
|
||||||
tkns = append(tkns, d.Token())
|
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
tkns = append(tkns, d.Token())
|
tkns = append(tkns, d.Token())
|
||||||
}
|
}
|
||||||
|
@ -245,10 +245,14 @@ func (d *Dispenser) NewFromNextTokens() *Dispenser {
|
||||||
|
|
||||||
// Token returns the current token.
|
// Token returns the current token.
|
||||||
func (d *Dispenser) Token() Token {
|
func (d *Dispenser) Token() Token {
|
||||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
return d.TokenAt(d.cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dispenser) TokenAt(cursor int) Token {
|
||||||
|
if cursor < 0 || cursor >= len(d.tokens) {
|
||||||
return Token{}
|
return Token{}
|
||||||
}
|
}
|
||||||
return d.tokens[d.cursor]
|
return d.tokens[cursor]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor returns the current cursor (token index).
|
// Cursor returns the current cursor (token index).
|
||||||
|
@ -256,6 +260,10 @@ func (d *Dispenser) Cursor() int {
|
||||||
return d.cursor
|
return d.cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Dispenser) Reset() {
|
||||||
|
d.cursor = -1
|
||||||
|
}
|
||||||
|
|
||||||
// ArgErr returns an argument error, meaning that another
|
// ArgErr returns an argument error, meaning that another
|
||||||
// argument was expected but not found. In other words,
|
// argument was expected but not found. In other words,
|
||||||
// a line break or open curly brace was encountered instead of
|
// a line break or open curly brace was encountered instead of
|
||||||
|
|
|
@ -28,12 +28,12 @@ import (
|
||||||
// Directives that do not appear in validDirectives will cause
|
// Directives that do not appear in validDirectives will cause
|
||||||
// an error. If you do not want to check for valid directives,
|
// an error. If you do not want to check for valid directives,
|
||||||
// pass in nil instead.
|
// pass in nil instead.
|
||||||
func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
|
func Parse(filename string, input io.Reader) ([]ServerBlock, error) {
|
||||||
tokens, err := allTokens(input)
|
tokens, err := allTokens(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p := parser{Dispenser: NewDispenser(filename, tokens), validDirectives: validDirectives}
|
p := parser{Dispenser: NewDispenser(filename, tokens)}
|
||||||
return p.parseAll()
|
return p.parseAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ func allTokens(input io.Reader) ([]Token, error) {
|
||||||
type parser struct {
|
type parser struct {
|
||||||
*Dispenser
|
*Dispenser
|
||||||
block ServerBlock // current server block being parsed
|
block ServerBlock // current server block being parsed
|
||||||
validDirectives []string // a directive must be valid or it's an error
|
|
||||||
eof bool // if we encounter a valid EOF in a hard place
|
eof bool // if we encounter a valid EOF in a hard place
|
||||||
definedSnippets map[string][]Token
|
definedSnippets map[string][]Token
|
||||||
|
nesting int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseAll() ([]ServerBlock, error) {
|
func (p *parser) parseAll() ([]ServerBlock, error) {
|
||||||
|
@ -72,14 +72,16 @@ func (p *parser) parseAll() ([]ServerBlock, error) {
|
||||||
if len(p.block.Keys) > 0 {
|
if len(p.block.Keys) > 0 {
|
||||||
blocks = append(blocks, p.block)
|
blocks = append(blocks, p.block)
|
||||||
}
|
}
|
||||||
|
if p.nesting > 0 {
|
||||||
|
return blocks, p.EOFErr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks, nil
|
return blocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseOne() error {
|
func (p *parser) parseOne() error {
|
||||||
p.block = ServerBlock{Tokens: make(map[string][]Token)}
|
p.block = ServerBlock{}
|
||||||
|
|
||||||
return p.begin()
|
return p.begin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +188,7 @@ func (p *parser) blockContents() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only look for close curly brace if there was an opening
|
// only look for close curly brace if there was an opening
|
||||||
if errOpenCurlyBrace == nil {
|
if errOpenCurlyBrace == nil {
|
||||||
err = p.closeCurlyBrace()
|
err = p.closeCurlyBrace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -205,6 +207,7 @@ func (p *parser) directives() error {
|
||||||
for p.Next() {
|
for p.Next() {
|
||||||
// end of server block
|
// end of server block
|
||||||
if p.Val() == "}" {
|
if p.Val() == "}" {
|
||||||
|
// p.nesting has already been decremented
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,11 +221,15 @@ func (p *parser) directives() error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// normal case: parse a directive on this line
|
// normal case: parse a directive as a new segment
|
||||||
|
// (a "segment" is a line which starts with a directive
|
||||||
|
// and which ends at the end of the line or at the end of
|
||||||
|
// the block that is opened at the end of the line)
|
||||||
if err := p.directive(); err != nil {
|
if err := p.directive(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,25 +352,24 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
|
||||||
// are loaded into the current server block for later use
|
// are loaded into the current server block for later use
|
||||||
// by directive setup functions.
|
// by directive setup functions.
|
||||||
func (p *parser) directive() error {
|
func (p *parser) directive() error {
|
||||||
dir := replaceEnvVars(p.Val())
|
// evaluate any env vars in directive token
|
||||||
nesting := 0
|
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
|
||||||
|
|
||||||
if !p.validDirective(dir) {
|
// a segment is a list of tokens associated with this directive
|
||||||
return p.Errf("Unknown directive '%s'", dir)
|
var segment Segment
|
||||||
}
|
|
||||||
|
|
||||||
// The directive itself is appended as a relevant token
|
// the directive itself is appended as a relevant token
|
||||||
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
|
segment = append(segment, p.Token())
|
||||||
|
|
||||||
for p.Next() {
|
for p.Next() {
|
||||||
if p.Val() == "{" {
|
if p.Val() == "{" {
|
||||||
nesting++
|
p.nesting++
|
||||||
} else if p.isNewLine() && nesting == 0 {
|
} else if p.isNewLine() && p.nesting == 0 {
|
||||||
p.cursor-- // read too far
|
p.cursor-- // read too far
|
||||||
break
|
break
|
||||||
} else if p.Val() == "}" && nesting > 0 {
|
} else if p.Val() == "}" && p.nesting > 0 {
|
||||||
nesting--
|
p.nesting--
|
||||||
} else if p.Val() == "}" && nesting == 0 {
|
} else if p.Val() == "}" && p.nesting == 0 {
|
||||||
return p.Err("Unexpected '}' because no matching opening brace")
|
return p.Err("Unexpected '}' because no matching opening brace")
|
||||||
} else if p.Val() == "import" && p.isNewLine() {
|
} else if p.Val() == "import" && p.isNewLine() {
|
||||||
if err := p.doImport(); err != nil {
|
if err := p.doImport(); err != nil {
|
||||||
|
@ -373,12 +379,15 @@ func (p *parser) directive() error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
|
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
|
||||||
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
|
segment = append(segment, p.Token())
|
||||||
}
|
}
|
||||||
|
|
||||||
if nesting > 0 {
|
p.block.Segments = append(p.block.Segments, segment)
|
||||||
|
|
||||||
|
if p.nesting > 0 {
|
||||||
return p.EOFErr()
|
return p.EOFErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,19 +413,6 @@ func (p *parser) closeCurlyBrace() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validDirective returns true if dir is in p.validDirectives.
|
|
||||||
func (p *parser) validDirective(dir string) bool {
|
|
||||||
if p.validDirectives == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, d := range p.validDirectives {
|
|
||||||
if d == dir {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceEnvVars replaces environment variables that appear in the token
|
// replaceEnvVars replaces environment variables that appear in the token
|
||||||
// and understands both the $UNIX and %WINDOWS% syntaxes.
|
// and understands both the $UNIX and %WINDOWS% syntaxes.
|
||||||
func replaceEnvVars(s string) string {
|
func replaceEnvVars(s string) string {
|
||||||
|
@ -447,13 +443,6 @@ func replaceEnvReferences(s, refStart, refEnd string) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerBlock associates any number of keys (usually addresses
|
|
||||||
// of some sort) with tokens (grouped by directive name).
|
|
||||||
type ServerBlock struct {
|
|
||||||
Keys []string
|
|
||||||
Tokens map[string][]Token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) isSnippet() (bool, string) {
|
func (p *parser) isSnippet() (bool, string) {
|
||||||
keys := p.block.Keys
|
keys := p.block.Keys
|
||||||
// A snippet block is a single key with parens. Nothing else qualifies.
|
// A snippet block is a single key with parens. Nothing else qualifies.
|
||||||
|
@ -480,6 +469,7 @@ func (p *parser) snippetTokens() ([]Token, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if p.Val() == "{" {
|
if p.Val() == "{" {
|
||||||
|
p.nesting++
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
tokens = append(tokens, p.tokens[p.cursor])
|
tokens = append(tokens, p.tokens[p.cursor])
|
||||||
|
@ -490,3 +480,43 @@ func (p *parser) snippetTokens() ([]Token, error) {
|
||||||
}
|
}
|
||||||
return tokens, nil
|
return tokens, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerBlock associates any number of keys from the
|
||||||
|
// head of the server block with tokens, which are
|
||||||
|
// grouped by segments.
|
||||||
|
type ServerBlock struct {
|
||||||
|
Keys []string
|
||||||
|
Segments []Segment
|
||||||
|
}
|
||||||
|
|
||||||
|
// DispenseDirective returns a dispenser that contains
|
||||||
|
// all the tokens in the server block.
|
||||||
|
func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
|
||||||
|
var tokens []Token
|
||||||
|
for _, seg := range sb.Segments {
|
||||||
|
if len(seg) > 0 && seg[0].Text == dir {
|
||||||
|
tokens = append(tokens, seg...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NewDispenser("", tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment is a list of tokens which begins with a directive
|
||||||
|
// and ends at the end of the directive (either at the end of
|
||||||
|
// the line, or at the end of a block it opens).
|
||||||
|
type Segment []Token
|
||||||
|
|
||||||
|
// Directive returns the directive name for the segment.
|
||||||
|
// The directive name is the text of the first token.
|
||||||
|
func (s Segment) Directive() string {
|
||||||
|
if len(s) > 0 {
|
||||||
|
return s[0].Text
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDispenser returns a dispenser for this
|
||||||
|
// segment's tokens.
|
||||||
|
func (s Segment) NewDispenser() *Dispenser {
|
||||||
|
return NewDispenser("", s)
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: re-enable all tests
|
||||||
|
|
||||||
func TestAllTokens(t *testing.T) {
|
func TestAllTokens(t *testing.T) {
|
||||||
input := strings.NewReader("a b c\nd e")
|
input := strings.NewReader("a b c\nd e")
|
||||||
expected := []string{"a", "b", "c", "d", "e"}
|
expected := []string{"a", "b", "c", "d", "e"}
|
||||||
|
@ -53,84 +55,67 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
input string
|
input string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
keys []string
|
keys []string
|
||||||
tokens map[string]int // map of directive name to number of tokens expected
|
numTokens []int // number of tokens to expect in each segment
|
||||||
}{
|
}{
|
||||||
{`localhost`, false, []string{
|
{`localhost`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{}},
|
}, []int{}},
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1`, false, []string{
|
dir1`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{1}},
|
||||||
"dir1": 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost:1234
|
{`localhost:1234
|
||||||
dir1 foo bar`, false, []string{
|
dir1 foo bar`, false, []string{
|
||||||
"localhost:1234",
|
"localhost:1234",
|
||||||
}, map[string]int{
|
}, []int{3},
|
||||||
"dir1": 3,
|
},
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost {
|
{`localhost {
|
||||||
dir1
|
dir1
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{1}},
|
||||||
"dir1": 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost:1234 {
|
{`localhost:1234 {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
dir2
|
dir2
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"localhost:1234",
|
"localhost:1234",
|
||||||
}, map[string]int{
|
}, []int{3, 1}},
|
||||||
"dir1": 3,
|
|
||||||
"dir2": 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`http://localhost https://localhost
|
{`http://localhost https://localhost
|
||||||
dir1 foo bar`, false, []string{
|
dir1 foo bar`, false, []string{
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"https://localhost",
|
"https://localhost",
|
||||||
}, map[string]int{
|
}, []int{3}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`http://localhost https://localhost {
|
{`http://localhost https://localhost {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"https://localhost",
|
"https://localhost",
|
||||||
}, map[string]int{
|
}, []int{3}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`http://localhost, https://localhost {
|
{`http://localhost, https://localhost {
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"https://localhost",
|
"https://localhost",
|
||||||
}, map[string]int{
|
}, []int{3}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`http://localhost, {
|
{`http://localhost, {
|
||||||
}`, true, []string{
|
}`, true, []string{
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
}, map[string]int{}},
|
}, []int{}},
|
||||||
|
|
||||||
{`host1:80, http://host2.com
|
{`host1:80, http://host2.com
|
||||||
dir1 foo bar
|
dir1 foo bar
|
||||||
dir2 baz`, false, []string{
|
dir2 baz`, false, []string{
|
||||||
"host1:80",
|
"host1:80",
|
||||||
"http://host2.com",
|
"http://host2.com",
|
||||||
}, map[string]int{
|
}, []int{3, 2}},
|
||||||
"dir1": 3,
|
|
||||||
"dir2": 2,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`http://host1.com,
|
{`http://host1.com,
|
||||||
http://host2.com,
|
http://host2.com,
|
||||||
|
@ -138,7 +123,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
"http://host1.com",
|
"http://host1.com",
|
||||||
"http://host2.com",
|
"http://host2.com",
|
||||||
"https://host3.com",
|
"https://host3.com",
|
||||||
}, map[string]int{}},
|
}, []int{}},
|
||||||
|
|
||||||
{`http://host1.com:1234, https://host2.com
|
{`http://host1.com:1234, https://host2.com
|
||||||
dir1 foo {
|
dir1 foo {
|
||||||
|
@ -147,10 +132,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
dir2`, false, []string{
|
dir2`, false, []string{
|
||||||
"http://host1.com:1234",
|
"http://host1.com:1234",
|
||||||
"https://host2.com",
|
"https://host2.com",
|
||||||
}, map[string]int{
|
}, []int{6, 1}},
|
||||||
"dir1": 6,
|
|
||||||
"dir2": 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`127.0.0.1
|
{`127.0.0.1
|
||||||
dir1 {
|
dir1 {
|
||||||
|
@ -160,34 +142,25 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
foo bar
|
foo bar
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
}, map[string]int{
|
}, []int{5, 5}},
|
||||||
"dir1": 5,
|
|
||||||
"dir2": 5,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
foo`, true, []string{
|
foo`, true, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{3}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
}`, false, []string{
|
}`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{3}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
} }`, true, []string{
|
} }`, true, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{}},
|
||||||
"dir1": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 {
|
dir1 {
|
||||||
|
@ -197,48 +170,38 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
}
|
}
|
||||||
dir2 foo bar`, false, []string{
|
dir2 foo bar`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{7, 3}},
|
||||||
"dir1": 7,
|
|
||||||
"dir2": 3,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{``, false, []string{}, map[string]int{}},
|
{``, false, []string{}, []int{}},
|
||||||
|
|
||||||
{`localhost
|
{`localhost
|
||||||
dir1 arg1
|
dir1 arg1
|
||||||
import testdata/import_test1.txt`, false, []string{
|
import testdata/import_test1.txt`, false, []string{
|
||||||
"localhost",
|
"localhost",
|
||||||
}, map[string]int{
|
}, []int{2, 3, 1}},
|
||||||
"dir1": 2,
|
|
||||||
"dir2": 3,
|
|
||||||
"dir3": 1,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`import testdata/import_test2.txt`, false, []string{
|
{`import testdata/import_test2.txt`, false, []string{
|
||||||
"host1",
|
"host1",
|
||||||
}, map[string]int{
|
}, []int{1, 2}},
|
||||||
"dir1": 1,
|
|
||||||
"dir2": 2,
|
|
||||||
}},
|
|
||||||
|
|
||||||
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}},
|
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, []int{}},
|
||||||
|
|
||||||
{`import testdata/not_found.txt`, true, []string{}, map[string]int{}},
|
{`import testdata/not_found.txt`, true, []string{}, []int{}},
|
||||||
|
|
||||||
{`""`, false, []string{}, map[string]int{}},
|
{`""`, false, []string{}, []int{}},
|
||||||
|
|
||||||
{``, false, []string{}, map[string]int{}},
|
{``, false, []string{}, []int{}},
|
||||||
|
|
||||||
// test cases found by fuzzing!
|
// test cases found by fuzzing!
|
||||||
{`import }{$"`, true, []string{}, map[string]int{}},
|
{`import }{$"`, true, []string{}, []int{}},
|
||||||
{`import /*/*.txt`, true, []string{}, map[string]int{}},
|
{`import /*/*.txt`, true, []string{}, []int{}},
|
||||||
{`import /???/?*?o`, true, []string{}, map[string]int{}},
|
{`import /???/?*?o`, true, []string{}, []int{}},
|
||||||
{`import /??`, true, []string{}, map[string]int{}},
|
{`import /??`, true, []string{}, []int{}},
|
||||||
{`import /[a-z]`, true, []string{}, map[string]int{}},
|
{`import /[a-z]`, true, []string{}, []int{}},
|
||||||
{`import {$}`, true, []string{}, map[string]int{}},
|
{`import {$}`, true, []string{}, []int{}},
|
||||||
{`import {%}`, true, []string{}, map[string]int{}},
|
{`import {%}`, true, []string{}, []int{}},
|
||||||
{`import {$$}`, true, []string{}, map[string]int{}},
|
{`import {$$}`, true, []string{}, []int{}},
|
||||||
{`import {%%}`, true, []string{}, map[string]int{}},
|
{`import {%%}`, true, []string{}, []int{}},
|
||||||
} {
|
} {
|
||||||
result, err := testParseOne(test.input)
|
result, err := testParseOne(test.input)
|
||||||
|
|
||||||
|
@ -261,15 +224,16 @@ func TestParseOneAndImport(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result.Tokens) != len(test.tokens) {
|
if len(result.Segments) != len(test.numTokens) {
|
||||||
t.Errorf("Test %d: Expected %d directives, had %d",
|
t.Errorf("Test %d: Expected %d segments, had %d",
|
||||||
i, len(test.tokens), len(result.Tokens))
|
i, len(test.numTokens), len(result.Segments))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for directive, tokens := range result.Tokens {
|
|
||||||
if len(tokens) != test.tokens[directive] {
|
for j, seg := range result.Segments {
|
||||||
t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
|
if len(seg) != test.numTokens[j] {
|
||||||
i, directive, test.tokens[directive], len(tokens))
|
t.Errorf("Test %d, segment %d: Expected %d tokens, counted %d",
|
||||||
|
i, j, test.numTokens[j], len(seg))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,12 +253,12 @@ func TestRecursiveImport(t *testing.T) {
|
||||||
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
|
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(got.Tokens) != 2 {
|
if len(got.Segments) != 2 {
|
||||||
t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
|
t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 {
|
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
|
||||||
t.Errorf("got unexpect tokens: %v", got.Tokens)
|
t.Errorf("got unexpect tokens: %v", got.Segments)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -384,12 +348,12 @@ func TestDirectiveImport(t *testing.T) {
|
||||||
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
|
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(got.Tokens) != 2 {
|
if len(got.Segments) != 2 {
|
||||||
t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
|
t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 {
|
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
|
||||||
t.Errorf("got unexpect tokens: %v", got.Tokens)
|
t.Errorf("got unexpect tokens: %v", got.Segments)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -557,21 +521,21 @@ func TestEnvironmentReplacement(t *testing.T) {
|
||||||
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
|
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
|
||||||
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][1].Text, "foobar"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// combined windows env vars in argument
|
// combined windows env vars in argument
|
||||||
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
|
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
|
||||||
blocks, _ = p.parseAll()
|
blocks, _ = p.parseAll()
|
||||||
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][1].Text, "servername.com/foobar"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// malformed env var (windows)
|
// malformed env var (windows)
|
||||||
p = testParser(":1234\ndir1 {%ADDRESS}")
|
p = testParser(":1234\ndir1 {%ADDRESS}")
|
||||||
blocks, _ = p.parseAll()
|
blocks, _ = p.parseAll()
|
||||||
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][1].Text, "{%ADDRESS}"; expected != actual {
|
||||||
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,22 +549,18 @@ func TestEnvironmentReplacement(t *testing.T) {
|
||||||
// in quoted field
|
// in quoted field
|
||||||
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
|
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
|
||||||
blocks, _ = p.parseAll()
|
blocks, _ = p.parseAll()
|
||||||
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][1].Text, "Test foobar test"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
// after end token
|
// after end token
|
||||||
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
|
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
|
||||||
blocks, _ = p.parseAll()
|
blocks, _ = p.parseAll()
|
||||||
if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][1].Text, "{{ .Name }} foobar"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParser(input string) parser {
|
|
||||||
return parser{Dispenser: newTestDispenser(input)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSnippets(t *testing.T) {
|
func TestSnippets(t *testing.T) {
|
||||||
p := testParser(`
|
p := testParser(`
|
||||||
(common) {
|
(common) {
|
||||||
|
@ -617,7 +577,7 @@ func TestSnippets(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
t.Log(b.Keys)
|
t.Log(b.Keys)
|
||||||
t.Log(b.Tokens)
|
t.Log(b.Segments)
|
||||||
}
|
}
|
||||||
if len(blocks) != 1 {
|
if len(blocks) != 1 {
|
||||||
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
||||||
|
@ -625,16 +585,15 @@ func TestSnippets(t *testing.T) {
|
||||||
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
|
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
|
||||||
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
if len(blocks[0].Tokens) != 2 {
|
if len(blocks[0].Segments) != 2 {
|
||||||
t.Fatalf("Server block should have tokens from import")
|
t.Fatalf("Server block should have tokens from import, got: %+v", blocks[0])
|
||||||
}
|
}
|
||||||
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual {
|
if actual, expected := blocks[0].Segments[1][1].Text, "stderr"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
|
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
|
||||||
|
@ -666,9 +625,9 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
t.Log(b.Keys)
|
t.Log(b.Keys)
|
||||||
t.Log(b.Tokens)
|
t.Log(b.Segments)
|
||||||
}
|
}
|
||||||
auth := blocks[0].Tokens["basicauth"]
|
auth := blocks[0].Segments[0]
|
||||||
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
|
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
|
||||||
if line != "basicauth / import password" {
|
if line != "basicauth / import password" {
|
||||||
// Previously, it would be changed to:
|
// Previously, it would be changed to:
|
||||||
|
@ -701,7 +660,7 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, b := range blocks {
|
for _, b := range blocks {
|
||||||
t.Log(b.Keys)
|
t.Log(b.Keys)
|
||||||
t.Log(b.Tokens)
|
t.Log(b.Segments)
|
||||||
}
|
}
|
||||||
if len(blocks) != 1 {
|
if len(blocks) != 1 {
|
||||||
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
||||||
|
@ -709,10 +668,14 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
|
||||||
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
|
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
|
||||||
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
if len(blocks[0].Tokens) != 1 {
|
if len(blocks[0].Segments) != 1 {
|
||||||
t.Fatalf("Server block should have tokens from import")
|
t.Fatalf("Server block should have tokens from import")
|
||||||
}
|
}
|
||||||
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
|
if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
|
||||||
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testParser(input string) parser {
|
||||||
|
return parser{Dispenser: newTestDispenser(input)}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"github.com/mholt/certmagic"
|
"github.com/mholt/certmagic"
|
||||||
)
|
)
|
||||||
|
@ -73,8 +73,8 @@ import (
|
||||||
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
||||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||||
// (Doing this is essentially a map-reduce technique.)
|
// (Doing this is essentially a map-reduce technique.)
|
||||||
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.ServerBlock) (map[string][]caddyfile.ServerBlock, error) {
|
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock) (map[string][]serverBlock, error) {
|
||||||
sbmap := make(map[string][]caddyfile.ServerBlock)
|
sbmap := make(map[string][]serverBlock)
|
||||||
|
|
||||||
for i, sblock := range originalServerBlocks {
|
for i, sblock := range originalServerBlocks {
|
||||||
// within a server block, we need to map all the listener addresses
|
// within a server block, we need to map all the listener addresses
|
||||||
|
@ -83,7 +83,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
|
||||||
// key of a server block as its own, but without having to repeat its
|
// key of a server block as its own, but without having to repeat its
|
||||||
// contents in cases where multiple keys really can be served together
|
// contents in cases where multiple keys really can be served together
|
||||||
addrToKeys := make(map[string][]string)
|
addrToKeys := make(map[string][]string)
|
||||||
for j, key := range sblock.Keys {
|
for j, key := range sblock.block.Keys {
|
||||||
// a key can have multiple listener addresses if there are multiple
|
// a key can have multiple listener addresses if there are multiple
|
||||||
// arguments to the 'bind' directive (although they will all have
|
// arguments to the 'bind' directive (although they will all have
|
||||||
// the same port, since the port is defined by the key or is implicit
|
// the same port, since the port is defined by the key or is implicit
|
||||||
|
@ -105,9 +105,12 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
|
||||||
// server block are only the ones which use the address; but
|
// server block are only the ones which use the address; but
|
||||||
// the contents (tokens) are of course the same
|
// the contents (tokens) are of course the same
|
||||||
for addr, keys := range addrToKeys {
|
for addr, keys := range addrToKeys {
|
||||||
sbmap[addr] = append(sbmap[addr], caddyfile.ServerBlock{
|
sbmap[addr] = append(sbmap[addr], serverBlock{
|
||||||
|
block: caddyfile.ServerBlock{
|
||||||
Keys: keys,
|
Keys: keys,
|
||||||
Tokens: sblock.Tokens,
|
Segments: sblock.block.Segments,
|
||||||
|
},
|
||||||
|
pile: sblock.pile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +126,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []caddyfile.
|
||||||
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
||||||
// association from multiple addresses to multiple server blocks; i.e. each element of
|
// association from multiple addresses to multiple server blocks; i.e. each element of
|
||||||
// the returned slice) becomes a server definition in the output JSON.
|
// the returned slice) becomes a server definition in the output JSON.
|
||||||
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]caddyfile.ServerBlock) []sbAddrAssociation {
|
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
|
||||||
var sbaddrs []sbAddrAssociation
|
var sbaddrs []sbAddrAssociation
|
||||||
for addr, sblocks := range addrToServerBlocks {
|
for addr, sblocks := range addrToServerBlocks {
|
||||||
// we start with knowing that at least this address
|
// we start with knowing that at least this address
|
||||||
|
@ -151,11 +154,12 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]ca
|
||||||
return sbaddrs
|
return sbaddrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBlock, key string) ([]string, error) {
|
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string) ([]string, error) {
|
||||||
addr, err := standardizeAddress(key)
|
addr, err := ParseAddress(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing key: %v", err)
|
return nil, fmt.Errorf("parsing key: %v", err)
|
||||||
}
|
}
|
||||||
|
addr = addr.Normalize()
|
||||||
|
|
||||||
lnPort := defaultPort
|
lnPort := defaultPort
|
||||||
if addr.Port != "" {
|
if addr.Port != "" {
|
||||||
|
@ -168,11 +172,8 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock caddyfile.ServerBloc
|
||||||
|
|
||||||
// the bind directive specifies hosts, but is optional
|
// the bind directive specifies hosts, but is optional
|
||||||
var lnHosts []string
|
var lnHosts []string
|
||||||
for i, token := range sblock.Tokens["bind"] {
|
for _, cfgVal := range sblock.pile["bind"] {
|
||||||
if i == 0 {
|
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
||||||
continue
|
|
||||||
}
|
|
||||||
lnHosts = append(lnHosts, token.Text)
|
|
||||||
}
|
}
|
||||||
if len(lnHosts) == 0 {
|
if len(lnHosts) == 0 {
|
||||||
lnHosts = []string{""}
|
lnHosts = []string{""}
|
||||||
|
@ -205,7 +206,53 @@ type Address struct {
|
||||||
Original, Scheme, Host, Port, Path string
|
Original, Scheme, Host, Port, Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a human-friendly print of the address.
|
// ParseAddress parses an address string into a structured format with separate
|
||||||
|
// scheme, host, port, and path portions, as well as the original input string.
|
||||||
|
func ParseAddress(str string) (Address, error) {
|
||||||
|
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
|
||||||
|
|
||||||
|
input := str
|
||||||
|
|
||||||
|
// Split input into components (prepend with // to force host portion by default)
|
||||||
|
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
|
||||||
|
str = "//" + str
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
return Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// separate host and port
|
||||||
|
host, port, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
host, port, err = net.SplitHostPort(u.Host + ":")
|
||||||
|
if err != nil {
|
||||||
|
host = u.Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if we can set port based off scheme
|
||||||
|
if port == "" {
|
||||||
|
if u.Scheme == "http" {
|
||||||
|
port = httpPort
|
||||||
|
} else if u.Scheme == "https" {
|
||||||
|
port = httpsPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// error if scheme and port combination violate convention
|
||||||
|
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
|
||||||
|
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: which of the methods on Address are even used?
|
||||||
|
|
||||||
|
// String returns a human-readable form of a. It will
|
||||||
|
// be a cleaned-up and filled-out URL string.
|
||||||
func (a Address) String() string {
|
func (a Address) String() string {
|
||||||
if a.Host == "" && a.Port == "" {
|
if a.Host == "" && a.Port == "" {
|
||||||
return ""
|
return ""
|
||||||
|
@ -235,16 +282,7 @@ func (a Address) String() string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// VHost returns a sensible concatenation of Host:Port/Path from a.
|
// Normalize returns a normalized version of a.
|
||||||
// It's basically the a.Original but without the scheme.
|
|
||||||
func (a Address) VHost() string {
|
|
||||||
if idx := strings.Index(a.Original, "://"); idx > -1 {
|
|
||||||
return a.Original[idx+3:]
|
|
||||||
}
|
|
||||||
return a.Original
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize normalizes URL: turn scheme and host names into lower case
|
|
||||||
func (a Address) Normalize() Address {
|
func (a Address) Normalize() Address {
|
||||||
path := a.Path
|
path := a.Path
|
||||||
if !caseSensitivePath {
|
if !caseSensitivePath {
|
||||||
|
@ -266,8 +304,8 @@ func (a Address) Normalize() Address {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key is similar to String, just replaces scheme and host values with modified values.
|
// Key returns a string form of a, much like String() does, but this
|
||||||
// Unlike String it doesn't add anything default (scheme, port, etc)
|
// method doesn't add anything default that wasn't in the original.
|
||||||
func (a Address) Key() string {
|
func (a Address) Key() string {
|
||||||
res := ""
|
res := ""
|
||||||
if a.Scheme != "" {
|
if a.Scheme != "" {
|
||||||
|
@ -276,75 +314,19 @@ func (a Address) Key() string {
|
||||||
if a.Host != "" {
|
if a.Host != "" {
|
||||||
res += 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
|
// 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
|
res += ":" + a.Port
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if a.Path != "" {
|
if a.Path != "" {
|
||||||
res += a.Path
|
res += a.Path
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// standardizeAddress parses an address string into a structured format with separate
|
|
||||||
// scheme, host, port, and path portions, as well as the original input string.
|
|
||||||
func standardizeAddress(str string) (Address, error) {
|
|
||||||
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
|
|
||||||
|
|
||||||
input := str
|
|
||||||
|
|
||||||
// Split input into components (prepend with // to assert host by default)
|
|
||||||
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
|
|
||||||
str = "//" + str
|
|
||||||
}
|
|
||||||
u, err := url.Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
return Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// separate host and port
|
|
||||||
host, port, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
host, port, err = net.SplitHostPort(u.Host + ":")
|
|
||||||
if err != nil {
|
|
||||||
host = u.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// see if we can set port based off scheme
|
|
||||||
if port == "" {
|
|
||||||
if u.Scheme == "http" {
|
|
||||||
port = httpPort
|
|
||||||
} else if u.Scheme == "https" {
|
|
||||||
port = httpsPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// repeated or conflicting scheme is confusing, so error
|
|
||||||
if u.Scheme != "" && (port == "http" || port == "https") {
|
|
||||||
return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// error if scheme and port combination violate convention
|
|
||||||
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
|
|
||||||
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// standardize http and https ports to their respective port numbers
|
|
||||||
if port == "http" {
|
|
||||||
u.Scheme = "http"
|
|
||||||
port = httpPort
|
|
||||||
} else if port == "https" {
|
|
||||||
u.Scheme = "https"
|
|
||||||
port = httpsPort
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultPort = "2015"
|
defaultPort = "2015"
|
||||||
caseSensitivePath = false
|
caseSensitivePath = false // TODO: Used?
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,22 +1,11 @@
|
||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestStandardizeAddress(t *testing.T) {
|
func TestParseAddress(t *testing.T) {
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
input string
|
input string
|
||||||
scheme, host, port, path string
|
scheme, host, port, path string
|
||||||
|
@ -31,12 +20,13 @@ func TestStandardizeAddress(t *testing.T) {
|
||||||
{`[::1]`, "", "::1", "", "", false},
|
{`[::1]`, "", "::1", "", "", false},
|
||||||
{`[::1]:1234`, "", "::1", "1234", "", false},
|
{`[::1]:1234`, "", "::1", "1234", "", false},
|
||||||
{`:`, "", "", "", "", false},
|
{`:`, "", "", "", "", false},
|
||||||
{`localhost:http`, "http", "localhost", "80", "", false},
|
{`:http`, "", "", "", "", true},
|
||||||
{`localhost:https`, "https", "localhost", "443", "", false},
|
{`:https`, "", "", "", "", true},
|
||||||
{`:http`, "http", "", "80", "", false},
|
{`localhost:http`, "", "", "", "", true}, // using service name in port is verboten, as of Go 1.12.8
|
||||||
{`:https`, "https", "", "443", "", false},
|
{`localhost:https`, "", "", "", "", true},
|
||||||
{`http://localhost:https`, "", "", "", "", true}, // conflict
|
{`http://localhost:https`, "", "", "", "", true}, // conflict
|
||||||
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
|
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
|
||||||
|
{`host:https/path`, "", "", "", "", true},
|
||||||
{`http://localhost:443`, "", "", "", "", true}, // not conventional
|
{`http://localhost:443`, "", "", "", "", true}, // not conventional
|
||||||
{`https://localhost:80`, "", "", "", "", true}, // not conventional
|
{`https://localhost:80`, "", "", "", "", true}, // not conventional
|
||||||
{`http://localhost`, "http", "localhost", "80", "", false},
|
{`http://localhost`, "http", "localhost", "80", "", false},
|
||||||
|
@ -58,10 +48,9 @@ func TestStandardizeAddress(t *testing.T) {
|
||||||
{`http://host/path`, "http", "host", "80", "/path", false},
|
{`http://host/path`, "http", "host", "80", "/path", false},
|
||||||
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
|
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
|
||||||
{`host:80/path`, "", "host", "80", "/path", false},
|
{`host:80/path`, "", "host", "80", "/path", false},
|
||||||
{`host:https/path`, "https", "host", "443", "/path", false},
|
|
||||||
{`/path`, "", "", "", "/path", false},
|
{`/path`, "", "", "", "/path", false},
|
||||||
} {
|
} {
|
||||||
actual, err := standardizeAddress(test.input)
|
actual, err := ParseAddress(test.input)
|
||||||
|
|
||||||
if err != nil && !test.shouldErr {
|
if err != nil && !test.shouldErr {
|
||||||
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
|
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
|
||||||
|
@ -88,24 +77,6 @@ func TestStandardizeAddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddressVHost(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
addr Address
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{Address{Original: "host:1234"}, "host:1234"},
|
|
||||||
{Address{Original: "host:1234/foo"}, "host:1234/foo"},
|
|
||||||
{Address{Original: "host/foo"}, "host/foo"},
|
|
||||||
{Address{Original: "http://host/foo"}, "host/foo"},
|
|
||||||
{Address{Original: "https://host/foo"}, "host/foo"},
|
|
||||||
} {
|
|
||||||
actual := test.addr.VHost()
|
|
||||||
if actual != test.expected {
|
|
||||||
t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddressString(t *testing.T) {
|
func TestAddressString(t *testing.T) {
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
addr Address
|
addr Address
|
||||||
|
@ -127,3 +98,69 @@ func TestAddressString(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeyNormalization(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "http://host:1234/path",
|
||||||
|
expect: "http://host:1234/path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "HTTP://A/ABCDEF",
|
||||||
|
expect: "http://a/ABCDEF",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "A/ABCDEF",
|
||||||
|
expect: "a/ABCDEF",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "A:2015/Path",
|
||||||
|
expect: "a:2015/Path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ":80",
|
||||||
|
expect: ":80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ":443",
|
||||||
|
expect: ":443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ":1234",
|
||||||
|
expect: ":1234",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ":",
|
||||||
|
expect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[::]",
|
||||||
|
expect: "::",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range testCases {
|
||||||
|
addr, err := ParseAddress(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expect := tc.expect
|
||||||
|
if !caseSensitivePath {
|
||||||
|
// every other part of the address should be lowercased when normalized,
|
||||||
|
// so simply lower-case the whole thing to do case-insensitive comparison
|
||||||
|
// of the path as well
|
||||||
|
expect = strings.ToLower(expect)
|
||||||
|
}
|
||||||
|
if actual := addr.Normalize().Key(); actual != expect {
|
||||||
|
t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, expect)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,81 +19,209 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/modules/caddyhttp"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (st *ServerType) parseRoot(
|
func init() {
|
||||||
tkns []caddyfile.Token,
|
RegisterDirective("bind", parseBind)
|
||||||
matcherDefs map[string]map[string]json.RawMessage,
|
RegisterDirective("root", parseRoot)
|
||||||
warnings *[]caddyconfig.Warning,
|
RegisterDirective("tls", parseTLS)
|
||||||
) ([]caddyhttp.Route, error) {
|
RegisterHandlerDirective("redir", parseRedir)
|
||||||
var routes []caddyhttp.Route
|
}
|
||||||
|
|
||||||
matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
|
func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
|
var lnHosts []string
|
||||||
|
for h.Next() {
|
||||||
|
lnHosts = append(lnHosts, h.RemainingArgs()...)
|
||||||
|
}
|
||||||
|
return h.NewBindAddresses(lnHosts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRoot(h Helper) ([]ConfigValue, error) {
|
||||||
|
if !h.Next() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
matcherSet, ok, err := h.MatcherToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
for _, mst := range matchersAndTokens {
|
// no matcher token; oops
|
||||||
d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
|
h.Dispenser.Prev()
|
||||||
|
|
||||||
var root string
|
|
||||||
for d.Next() {
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
}
|
||||||
root = d.Val()
|
|
||||||
if d.NextArg() {
|
if !h.NextArg() {
|
||||||
return nil, d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
root := h.Val()
|
||||||
|
if h.NextArg() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
varsHandler := caddyhttp.VarsMiddleware{"root": root}
|
varsHandler := caddyhttp.VarsMiddleware{"root": root}
|
||||||
route := caddyhttp.Route{
|
route := caddyhttp.Route{
|
||||||
Handle: []json.RawMessage{
|
HandlersRaw: []json.RawMessage{
|
||||||
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", warnings),
|
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", nil),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if mst.matcherSet != nil {
|
if matcherSet != nil {
|
||||||
route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
|
route.MatcherSetsRaw = []map[string]json.RawMessage{matcherSet}
|
||||||
}
|
}
|
||||||
|
|
||||||
routes = append(routes, route)
|
return h.NewVarsRoute(route), nil
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ServerType) parseRedir(
|
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
tkns []caddyfile.Token,
|
var configVals []ConfigValue
|
||||||
matcherDefs map[string]map[string]json.RawMessage,
|
|
||||||
warnings *[]caddyconfig.Warning,
|
|
||||||
) ([]caddyhttp.Route, error) {
|
|
||||||
var routes []caddyhttp.Route
|
|
||||||
|
|
||||||
matchersAndTokens, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
|
cp := new(caddytls.ConnectionPolicy)
|
||||||
if err != nil {
|
var fileLoader caddytls.FileLoader
|
||||||
return nil, err
|
var folderLoader caddytls.FolderLoader
|
||||||
|
var mgr caddytls.ACMEManagerMaker
|
||||||
|
var off bool
|
||||||
|
|
||||||
|
for h.Next() {
|
||||||
|
// file certificate loader
|
||||||
|
firstLine := h.RemainingArgs()
|
||||||
|
switch len(firstLine) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
if firstLine[0] == "off" {
|
||||||
|
off = true
|
||||||
|
} else {
|
||||||
|
mgr.Email = firstLine[0]
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
|
||||||
|
Certificate: firstLine[0],
|
||||||
|
Key: firstLine[1],
|
||||||
|
// TODO: add tags, for enterprise module's certificate selection
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mst := range matchersAndTokens {
|
var hasBlock bool
|
||||||
var route caddyhttp.Route
|
for h.NextBlock() {
|
||||||
|
hasBlock = true
|
||||||
|
|
||||||
d := caddyfile.NewDispenser("Caddyfile", mst.tokens)
|
switch h.Val() {
|
||||||
|
|
||||||
for d.Next() {
|
// connection policy
|
||||||
if !d.NextArg() {
|
case "protocols":
|
||||||
return nil, d.ArgErr()
|
args := h.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, h.SyntaxErr("one or two protocols")
|
||||||
}
|
}
|
||||||
to := d.Val()
|
if len(args) > 0 {
|
||||||
|
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
|
||||||
|
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
||||||
|
}
|
||||||
|
cp.ProtocolMin = args[0]
|
||||||
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
|
||||||
|
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
||||||
|
}
|
||||||
|
cp.ProtocolMax = args[1]
|
||||||
|
}
|
||||||
|
case "ciphers":
|
||||||
|
for h.NextArg() {
|
||||||
|
if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
|
||||||
|
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||||
|
}
|
||||||
|
cp.CipherSuites = append(cp.CipherSuites, h.Val())
|
||||||
|
}
|
||||||
|
case "curves":
|
||||||
|
for h.NextArg() {
|
||||||
|
if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
|
||||||
|
return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
|
||||||
|
}
|
||||||
|
cp.Curves = append(cp.Curves, h.Val())
|
||||||
|
}
|
||||||
|
case "alpn":
|
||||||
|
args := h.RemainingArgs()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
cp.ALPN = args
|
||||||
|
|
||||||
|
// certificate folder loader
|
||||||
|
case "load":
|
||||||
|
folderLoader = append(folderLoader, h.RemainingArgs()...)
|
||||||
|
|
||||||
|
// automation policy
|
||||||
|
case "ca":
|
||||||
|
arg := h.RemainingArgs()
|
||||||
|
if len(arg) != 1 {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
mgr.CA = arg[0]
|
||||||
|
|
||||||
|
// TODO: other properties for automation manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
var code string
|
||||||
if d.NextArg() {
|
if h.NextArg() {
|
||||||
code = d.Val()
|
code = h.Val()
|
||||||
}
|
}
|
||||||
if code == "permanent" {
|
if code == "permanent" {
|
||||||
code = "301"
|
code = "301"
|
||||||
|
@ -119,139 +247,9 @@ func (st *ServerType) parseRedir(
|
||||||
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
|
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := caddyhttp.StaticResponse{
|
return caddyhttp.StaticResponse{
|
||||||
StatusCode: caddyhttp.WeakString(code),
|
StatusCode: caddyhttp.WeakString(code),
|
||||||
Headers: http.Header{"Location": []string{to}},
|
Headers: http.Header{"Location": []string{to}},
|
||||||
Body: body,
|
Body: body,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
route.Handle = append(route.Handle,
|
|
||||||
caddyconfig.JSONModuleObject(handler, "handler", "static_response", warnings))
|
|
||||||
}
|
|
||||||
|
|
||||||
if mst.matcherSet != nil {
|
|
||||||
route.MatcherSets = []map[string]json.RawMessage{mst.matcherSet}
|
|
||||||
}
|
|
||||||
|
|
||||||
routes = append(routes, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// a naked tls directive is not allowed
|
|
||||||
if len(firstLine) == 0 && !hasBlock {
|
|
||||||
return m, d.ArgErr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *ServerType) parseTLSCerts(d *caddyfile.Dispenser) (map[string]caddytls.CertificateLoader, error) {
|
|
||||||
var fileLoader caddytls.FileLoader
|
|
||||||
var folderLoader caddytls.FolderLoader
|
|
||||||
|
|
||||||
for d.Next() {
|
|
||||||
// file loader
|
|
||||||
firstLine := d.RemainingArgs()
|
|
||||||
if len(firstLine) == 2 {
|
|
||||||
fileLoader = append(fileLoader, caddytls.CertKeyFilePair{
|
|
||||||
Certificate: firstLine[0],
|
|
||||||
Key: firstLine[1],
|
|
||||||
// TODO: tags, for enterprise module's certificate selection
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// folder loader
|
|
||||||
for d.NextBlock() {
|
|
||||||
if d.Val() == "load" {
|
|
||||||
folderLoader = append(folderLoader, d.RemainingArgs()...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// put configured loaders into the map
|
|
||||||
loaders := make(map[string]caddytls.CertificateLoader)
|
|
||||||
if len(fileLoader) > 0 {
|
|
||||||
loaders["load_files"] = fileLoader
|
|
||||||
}
|
|
||||||
if len(folderLoader) > 0 {
|
|
||||||
loaders["load_folders"] = folderLoader
|
|
||||||
}
|
|
||||||
|
|
||||||
return loaders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *ServerType) parseTLSConnPolicy(d *caddyfile.Dispenser) (*caddytls.ConnectionPolicy, error) {
|
|
||||||
cp := new(caddytls.ConnectionPolicy)
|
|
||||||
|
|
||||||
for d.Next() {
|
|
||||||
for d.NextBlock() {
|
|
||||||
switch d.Val() {
|
|
||||||
case "protocols":
|
|
||||||
args := d.RemainingArgs()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return nil, d.SyntaxErr("one or two protocols")
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
|
|
||||||
return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
|
||||||
}
|
|
||||||
cp.ProtocolMin = args[0]
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
|
|
||||||
return nil, d.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
|
||||||
}
|
|
||||||
cp.ProtocolMax = args[1]
|
|
||||||
}
|
|
||||||
case "ciphers":
|
|
||||||
for d.NextArg() {
|
|
||||||
if _, ok := caddytls.SupportedCipherSuites[d.Val()]; !ok {
|
|
||||||
return nil, d.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", d.Val())
|
|
||||||
}
|
|
||||||
cp.CipherSuites = append(cp.CipherSuites, d.Val())
|
|
||||||
}
|
|
||||||
case "curves":
|
|
||||||
for d.NextArg() {
|
|
||||||
if _, ok := caddytls.SupportedCurves[d.Val()]; !ok {
|
|
||||||
return nil, d.Errf("Wrong curve name or curve not supported: '%s'", d.Val())
|
|
||||||
}
|
|
||||||
cp.Curves = append(cp.Curves, d.Val())
|
|
||||||
}
|
|
||||||
case "alpn":
|
|
||||||
args := d.RemainingArgs()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
cp.ALPN = args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cp, nil
|
|
||||||
}
|
}
|
||||||
|
|
182
caddyconfig/httpcaddyfile/directives.go
Normal file
182
caddyconfig/httpcaddyfile/directives.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package httpcaddyfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultDirectiveOrder specifies the order
|
||||||
|
// to apply directives in HTTP routes.
|
||||||
|
// TODO: finish the ability to customize this
|
||||||
|
var defaultDirectiveOrder = []string{
|
||||||
|
"rewrite",
|
||||||
|
"try_files",
|
||||||
|
"headers",
|
||||||
|
"encode",
|
||||||
|
"templates",
|
||||||
|
"redir",
|
||||||
|
"static_response", // TODO: "reply" or "respond"?
|
||||||
|
"reverse_proxy",
|
||||||
|
"file_server",
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDirective registers a unique directive dir with an
|
||||||
|
// associated unmarshaling (setup) function. When directive dir
|
||||||
|
// is encountered in a Caddyfile, setupFunc will be called to
|
||||||
|
// unmarshal its tokens.
|
||||||
|
func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
|
||||||
|
if _, ok := registeredDirectives[dir]; ok {
|
||||||
|
panic("directive " + dir + " already registered")
|
||||||
|
}
|
||||||
|
registeredDirectives[dir] = setupFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandlerDirective is like RegisterDirective, but for
|
||||||
|
// directives which specifically output only an HTTP handler.
|
||||||
|
func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
|
||||||
|
RegisterDirective(dir, func(h Helper) ([]ConfigValue, error) {
|
||||||
|
if !h.Next() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
matcherSet, ok, err := h.MatcherToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
h.Dispenser.Delete() // strip matcher token
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Dispenser.Reset() // pretend this lookahead never happened
|
||||||
|
val, err := setupFunc(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.NewRoute(matcherSet, val), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper is a type which helps setup a value from
|
||||||
|
// Caddyfile tokens.
|
||||||
|
type Helper struct {
|
||||||
|
*caddyfile.Dispenser
|
||||||
|
warnings *[]caddyconfig.Warning
|
||||||
|
matcherDefs map[string]map[string]json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON converts val into JSON. Any errors are added to warnings.
|
||||||
|
func (h Helper) JSON(val interface{}, warnings *[]caddyconfig.Warning) json.RawMessage {
|
||||||
|
return caddyconfig.JSON(val, h.warnings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherToken assumes the current token is (possibly) a matcher, and
|
||||||
|
// if so, returns the matcher set along with a true value. If the current
|
||||||
|
// token is not a matcher, nil and false is returned. Note that a true
|
||||||
|
// value may be returned with a nil matcher set if it is a catch-all.
|
||||||
|
func (h Helper) MatcherToken() (map[string]json.RawMessage, bool, error) {
|
||||||
|
if !h.NextArg() {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return matcherSetFromMatcherToken(h.Dispenser.Token(), h.matcherDefs, h.warnings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoute returns config values relevant to creating a new HTTP route.
|
||||||
|
func (h Helper) NewRoute(matcherSet map[string]json.RawMessage,
|
||||||
|
handler caddyhttp.MiddlewareHandler) []ConfigValue {
|
||||||
|
mod, err := caddy.GetModule(caddy.GetModuleName(handler))
|
||||||
|
if err != nil {
|
||||||
|
// TODO: append to warnings
|
||||||
|
}
|
||||||
|
var matcherSetsRaw []map[string]json.RawMessage
|
||||||
|
if matcherSet != nil {
|
||||||
|
matcherSetsRaw = append(matcherSetsRaw, matcherSet)
|
||||||
|
}
|
||||||
|
return []ConfigValue{
|
||||||
|
{
|
||||||
|
Class: "route",
|
||||||
|
Value: caddyhttp.Route{
|
||||||
|
MatcherSetsRaw: matcherSetsRaw,
|
||||||
|
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID(), h.warnings)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBindAddresses returns config values relevant to adding
|
||||||
|
// listener bind addresses to the config.
|
||||||
|
func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
|
||||||
|
return []ConfigValue{{Class: "bind", Value: addrs}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVarsRoute returns config values relevant to adding a
|
||||||
|
// "vars" wrapper route to the config.
|
||||||
|
func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue {
|
||||||
|
return []ConfigValue{{Class: "var", Value: route}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigValue represents a value to be added to the final
|
||||||
|
// configuration, or a value to be consulted when building
|
||||||
|
// the final configuration.
|
||||||
|
type ConfigValue struct {
|
||||||
|
// The kind of value this is. As the config is
|
||||||
|
// being built, the adapter will look in the
|
||||||
|
// "pile" for values belonging to a certain
|
||||||
|
// class when it is setting up a certain part
|
||||||
|
// of the config. The associated value will be
|
||||||
|
// type-asserted and placed accordingly.
|
||||||
|
Class string
|
||||||
|
|
||||||
|
// The value to be used when building the config.
|
||||||
|
// Generally its type is associated with the
|
||||||
|
// name of the Class.
|
||||||
|
Value interface{}
|
||||||
|
|
||||||
|
directive string
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverBlock pairs a Caddyfile server block
|
||||||
|
// with a "pile" of config values, keyed by class
|
||||||
|
// name.
|
||||||
|
type serverBlock struct {
|
||||||
|
block caddyfile.ServerBlock
|
||||||
|
pile map[string][]ConfigValue // config values obtained from directives
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// UnmarshalFunc is a function which can unmarshal Caddyfile
|
||||||
|
// tokens into zero or more config values using a Helper type.
|
||||||
|
// These are passed in a call to RegisterDirective.
|
||||||
|
UnmarshalFunc func(h Helper) ([]ConfigValue, error)
|
||||||
|
|
||||||
|
// UnmarshalHandlerFunc is like UnmarshalFunc, except the
|
||||||
|
// output of the unmarshaling is an HTTP handler. This
|
||||||
|
// function does not need to deal with HTTP request matching
|
||||||
|
// which is abstracted away. Since writing HTTP handlers
|
||||||
|
// with Caddyfile support is very common, this is a more
|
||||||
|
// convenient way to add a handler to the chain since a lot
|
||||||
|
// of the details common to HTTP handlers are taken care of
|
||||||
|
// for you. These are passed to a call to
|
||||||
|
// RegisterHandlerDirective.
|
||||||
|
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
var registeredDirectives = make(map[string]UnmarshalFunc)
|
|
@ -17,7 +17,6 @@ package httpcaddyfile
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
@ -55,38 +54,3 @@ func (st *ServerType) parseMatcherDefinitions(d *caddyfile.Dispenser) (map[strin
|
||||||
}
|
}
|
||||||
return matchers, nil
|
return matchers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// directiveBuckets returns a list of middleware/handler directives.
|
|
||||||
// Buckets are ordered, and directives should be evaluated in their
|
|
||||||
// bucket order. Within a bucket, directives are not ordered. Hence,
|
|
||||||
// the return value has a slice of buckets, where each bucket is a
|
|
||||||
// map, which is a strongly-typed reminder that directives within a
|
|
||||||
// bucket are not ordered.
|
|
||||||
func directiveBuckets() []map[string]struct{} {
|
|
||||||
directiveBuckets := []map[string]struct{}{
|
|
||||||
// prefer odd-numbered buckets; evens are there for contingencies
|
|
||||||
{}, // 0
|
|
||||||
{}, // 1 - keep empty unless necessary
|
|
||||||
{}, // 2
|
|
||||||
{}, // 3 - first handlers, last responders
|
|
||||||
{}, // 4
|
|
||||||
{}, // 5 - middle of chain
|
|
||||||
{}, // 6
|
|
||||||
{}, // 7 - last handlers, first responders
|
|
||||||
{}, // 8
|
|
||||||
{}, // 9 - keep empty unless necessary
|
|
||||||
{}, // 10
|
|
||||||
}
|
|
||||||
for _, mod := range caddy.GetModules("http.handlers") {
|
|
||||||
if hd, ok := mod.New().(HandlerDirective); ok {
|
|
||||||
bucket := hd.Bucket()
|
|
||||||
if bucket < 0 || bucket >= len(directiveBuckets) {
|
|
||||||
log.Printf("[ERROR] directive %s: bucket out of range [0-%d): %d; skipping",
|
|
||||||
mod.Name, len(directiveBuckets), bucket)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
directiveBuckets[bucket][mod.ID()] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return directiveBuckets
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,17 +17,18 @@ package httpcaddyfile
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/certmagic"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
|
"github.com/mholt/certmagic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -38,24 +39,57 @@ func init() {
|
||||||
type ServerType struct {
|
type ServerType struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidDirectives returns the list of known directives.
|
// TODO: error on unrecognized directives
|
||||||
func (ServerType) ValidDirectives() []string {
|
|
||||||
dirs := []string{"matcher", "root", "tls", "redir"} // TODO: put special-case (hard-coded, or non-handler) directives here
|
|
||||||
for _, mod := range caddy.GetModules("http.handlers") {
|
|
||||||
if _, ok := mod.New().(HandlerDirective); ok {
|
|
||||||
dirs = append(dirs, mod.ID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dirs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup makes a config from the tokens.
|
// Setup makes a config from the tokens.
|
||||||
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
|
options map[string]string) (*caddy.Config, []caddyconfig.Warning, error) {
|
||||||
var warnings []caddyconfig.Warning
|
var warnings []caddyconfig.Warning
|
||||||
|
|
||||||
|
var serverBlocks []serverBlock
|
||||||
|
for _, sblock := range originalServerBlocks {
|
||||||
|
serverBlocks = append(serverBlocks, serverBlock{
|
||||||
|
block: sblock,
|
||||||
|
pile: make(map[string][]ConfigValue),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sb := range serverBlocks {
|
||||||
|
// extract matcher definitions
|
||||||
|
d := sb.block.DispenseDirective("matcher")
|
||||||
|
matcherDefs, err := st.parseMatcherDefinitions(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, segment := range sb.block.Segments {
|
||||||
|
dir := segment.Directive()
|
||||||
|
if dir == "matcher" {
|
||||||
|
// TODO: This is a special case because we pre-processed it; handle this better
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dirFunc, ok := registeredDirectives[dir]; ok {
|
||||||
|
results, err := dirFunc(Helper{
|
||||||
|
Dispenser: segment.NewDispenser(),
|
||||||
|
warnings: &warnings,
|
||||||
|
matcherDefs: matcherDefs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||||
|
}
|
||||||
|
for _, result := range results {
|
||||||
|
result.directive = dir
|
||||||
|
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: this should be an error
|
||||||
|
log.Printf("%s not registered", dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// map
|
// map
|
||||||
sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks)
|
sbmap, err := st.mapAddressToServerBlocks(serverBlocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
@ -63,6 +97,22 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
// reduce
|
// reduce
|
||||||
pairings := st.consolidateAddrMappings(sbmap)
|
pairings := st.consolidateAddrMappings(sbmap)
|
||||||
|
|
||||||
|
// TODO: shorthand placeholders
|
||||||
|
// for _, p := range pairings {
|
||||||
|
// for _, sblock := range p.serverBlocks {
|
||||||
|
// for _, tokens := range sblock.Tokens {
|
||||||
|
// for i := 0; i < len(tokens); i++ {
|
||||||
|
// switch tokens[i].Text {
|
||||||
|
// case "{uri}":
|
||||||
|
// tokens[i].Text = "{http.request.uri}"
|
||||||
|
// case "{path}":
|
||||||
|
// tokens[i].Text = "{http.request.uri.path}"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// each pairing of listener addresses to list of server
|
// each pairing of listener addresses to list of server
|
||||||
// blocks is basically a server definition
|
// blocks is basically a server definition
|
||||||
servers, err := st.serversFromPairings(pairings, &warnings)
|
servers, err := st.serversFromPairings(pairings, &warnings)
|
||||||
|
@ -81,45 +131,33 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
|
||||||
tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
|
tlsApp := caddytls.TLS{Certificates: make(map[string]json.RawMessage)}
|
||||||
for _, p := range pairings {
|
for _, p := range pairings {
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
if tkns, ok := sblock.Tokens["tls"]; ok {
|
// tls automation policies
|
||||||
// extract all unique hostnames from the server block
|
if mmVals, ok := sblock.pile["tls.automation_manager"]; ok {
|
||||||
// keys, then convert to a slice for use in the TLS app
|
for _, mmVal := range mmVals {
|
||||||
hostMap := make(map[string]struct{})
|
mm := mmVal.Value.(caddytls.ManagerMaker)
|
||||||
for _, sblockKey := range sblock.Keys {
|
sblockHosts, err := st.autoHTTPSHosts(sblock)
|
||||||
addr, err := standardizeAddress(sblockKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, warnings, fmt.Errorf("parsing server block key: %v", err)
|
|
||||||
}
|
|
||||||
hostMap[addr.Host] = struct{}{}
|
|
||||||
}
|
|
||||||
sblockHosts := make([]string, 0, len(hostMap))
|
|
||||||
for host := range hostMap {
|
|
||||||
sblockHosts = append(sblockHosts, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse tokens to get ACME manager config
|
|
||||||
acmeMgr, err := st.parseTLSAutomationManager(caddyfile.NewDispenser("Caddyfile", tkns))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, caddytls.AutomationPolicy{
|
||||||
Hosts: sblockHosts,
|
Hosts: sblockHosts,
|
||||||
ManagementRaw: caddyconfig.JSONModuleObject(acmeMgr, "module", "acme", &warnings),
|
ManagementRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID(), &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)
|
tlsApp.Certificates[loaderName] = caddyconfig.JSON(loader, &warnings)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// consolidate automation policies that are the exact same
|
||||||
|
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
||||||
|
|
||||||
// annnd the top-level config, then we're done!
|
// annnd the top-level config, then we're done!
|
||||||
cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
|
cfg := &caddy.Config{AppsRaw: make(map[string]json.RawMessage)}
|
||||||
|
@ -140,10 +178,11 @@ func (st *ServerType) hostsFromServerBlockKeys(sb caddyfile.ServerBlock) ([]stri
|
||||||
// first get each unique hostname
|
// first get each unique hostname
|
||||||
hostMap := make(map[string]struct{})
|
hostMap := make(map[string]struct{})
|
||||||
for _, sblockKey := range sb.Keys {
|
for _, sblockKey := range sb.Keys {
|
||||||
addr, err := standardizeAddress(sblockKey)
|
addr, err := ParseAddress(sblockKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing server block key: %v", err)
|
return nil, fmt.Errorf("parsing server block key: %v", err)
|
||||||
}
|
}
|
||||||
|
addr = addr.Normalize()
|
||||||
hostMap[addr.Host] = struct{}{}
|
hostMap[addr.Host] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,59 +206,40 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
|
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock.block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.Keys, err)
|
return nil, fmt.Errorf("server block %v: compiling matcher sets: %v", sblock.block.Keys, err)
|
||||||
}
|
|
||||||
|
|
||||||
// extract matcher definitions
|
|
||||||
d := caddyfile.NewDispenser("Caddyfile", sblock.Tokens["matcher"])
|
|
||||||
matcherDefs, err := st.parseMatcherDefinitions(d)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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)
|
siteVarSubroute, handlerSubroute := new(caddyhttp.Subroute), new(caddyhttp.Subroute)
|
||||||
|
|
||||||
// built-in directives
|
// tls: connection policies and toggle auto HTTPS
|
||||||
|
|
||||||
// root: path to root of site
|
autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
|
||||||
if tkns, ok := sblock.Tokens["root"]; ok {
|
|
||||||
routes, err := st.parseRoot(tkns, matcherDefs, warnings)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
siteVarSubroute.Routes = append(siteVarSubroute.Routes, routes...)
|
if _, ok := sblock.pile["tls.off"]; ok {
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...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
|
// tls off: disable TLS (and automatic HTTPS) for server block's names
|
||||||
if srv.AutoHTTPS == nil {
|
if srv.AutoHTTPS == nil {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
}
|
}
|
||||||
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
|
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
|
||||||
} else {
|
} else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
|
||||||
// tls connection policies
|
// tls connection policies
|
||||||
cp, err := st.parseTLSConnPolicy(caddyfile.NewDispenser("Caddyfile", tkns))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: are matchers needed if every hostname of the config is matched?
|
// TODO: are matchers needed if every hostname of the config is matched?
|
||||||
cp.Matchers = map[string]json.RawMessage{
|
cp.Matchers = map[string]json.RawMessage{
|
||||||
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
|
"sni": caddyconfig.JSON(hosts, warnings), // make sure to match all hosts, not just auto-HTTPS-qualified ones
|
||||||
|
@ -227,61 +247,34 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
|
||||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: consolidate equal conn policies
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// set up each handler directive
|
||||||
for _, dirBucket := range directiveBuckets() {
|
dirRoutes := sblock.pile["route"]
|
||||||
for dir := range dirBucket {
|
// TODO: The ordering here depends on... if there is a list of
|
||||||
// keep in mind that multiple occurrences of the directive may appear here
|
// directives to use, then sort by that, otherwise just use in
|
||||||
tkns, ok := sblock.Tokens[dir]
|
// the order they appear in the slice (which is the order they
|
||||||
if !ok {
|
// appeared in the Caddyfile)
|
||||||
continue
|
sortByList := true
|
||||||
|
if sortByList {
|
||||||
|
dirPositions := make(map[string]int)
|
||||||
|
for i, dir := range defaultDirectiveOrder {
|
||||||
|
dirPositions[dir] = i
|
||||||
}
|
}
|
||||||
|
sort.SliceStable(dirRoutes, func(i, j int) bool {
|
||||||
// extract matcher sets from matcher tokens, if any
|
iDir, jDir := dirRoutes[i].directive, dirRoutes[j].directive
|
||||||
matcherSetsMap, err := st.tokensToMatcherSets(tkns, matcherDefs, warnings)
|
return dirPositions[iDir] < dirPositions[jDir]
|
||||||
|
})
|
||||||
mod, err := caddy.GetModule("http.handlers." + dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting handler module '%s': %v", mod.Name, err)
|
|
||||||
}
|
}
|
||||||
|
for _, r := range dirRoutes {
|
||||||
// the tokens have been divided by matcher set for us,
|
handlerSubroute.Routes = append(handlerSubroute.Routes, r.Value.(caddyhttp.Route))
|
||||||
// so iterate each one and set them up
|
|
||||||
for _, mst := range matcherSetsMap {
|
|
||||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("handler module '%s' is not a Caddyfile unmarshaler", mod.Name)
|
|
||||||
}
|
|
||||||
err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(d.File(), mst.tokens))
|
|
||||||
if err != nil {
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
handlerSubroute.Routes = append(handlerSubroute.Routes, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
handlerSubroute.Routes = append(handlerSubroute.Routes, routes...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the route that contains the site's handlers will
|
// the route that contains the site's handlers will
|
||||||
|
@ -298,7 +291,7 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
|
||||||
siteSubroute.Routes = append(
|
siteSubroute.Routes = append(
|
||||||
siteVarSubroute.Routes,
|
siteVarSubroute.Routes,
|
||||||
caddyhttp.Route{
|
caddyhttp.Route{
|
||||||
Handle: []json.RawMessage{
|
HandlersRaw: []json.RawMessage{
|
||||||
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
|
caddyconfig.JSONModuleObject(subSubRoute, "handler", "subroute", warnings),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -308,8 +301,8 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
|
||||||
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
|
siteSubroute.Routes = consolidateRoutes(siteSubroute.Routes)
|
||||||
|
|
||||||
srv.Routes = append(srv.Routes, caddyhttp.Route{
|
srv.Routes = append(srv.Routes, caddyhttp.Route{
|
||||||
MatcherSets: matcherSetsEnc,
|
MatcherSetsRaw: matcherSetsEnc,
|
||||||
Handle: []json.RawMessage{
|
HandlersRaw: []json.RawMessage{
|
||||||
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
|
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -323,16 +316,32 @@ func (st *ServerType) serversFromPairings(pairings []sbAddrAssociation, warnings
|
||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
|
||||||
|
// get the hosts for this server block...
|
||||||
|
hosts, err := st.hostsFromServerBlockKeys(sb.block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// ...and of those, which ones qualify for auto HTTPS
|
||||||
|
var autoHTTPSQualifiedHosts []string
|
||||||
|
for _, h := range hosts {
|
||||||
|
if certmagic.HostQualifies(h) {
|
||||||
|
autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return autoHTTPSQualifiedHosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
// consolidateRoutes combines routes with the same properties
|
// consolidateRoutes combines routes with the same properties
|
||||||
// (same matchers, same Terminal and Group settings) for a
|
// (same matchers, same Terminal and Group settings) for a
|
||||||
// cleaner overall output.
|
// cleaner overall output.
|
||||||
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
|
func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
|
||||||
for i := 0; i < len(routes)-1; i++ {
|
for i := 0; i < len(routes)-1; i++ {
|
||||||
if reflect.DeepEqual(routes[i].MatcherSets, routes[i+1].MatcherSets) &&
|
if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) &&
|
||||||
routes[i].Terminal == routes[i+1].Terminal &&
|
routes[i].Terminal == routes[i+1].Terminal &&
|
||||||
routes[i].Group == routes[i+1].Group {
|
routes[i].Group == routes[i+1].Group {
|
||||||
// keep the handlers in the same order, then splice out repetitive route
|
// keep the handlers in the same order, then splice out repetitive route
|
||||||
routes[i].Handle = append(routes[i].Handle, routes[i+1].Handle...)
|
routes[i].HandlersRaw = append(routes[i].HandlersRaw, routes[i+1].HandlersRaw...)
|
||||||
routes = append(routes[:i+1], routes[i+2:]...)
|
routes = append(routes[:i+1], routes[i+2:]...)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
|
@ -340,53 +349,26 @@ func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ServerType) tokensToMatcherSets(
|
// consolidateAutomationPolicies combines automation policies that are the same,
|
||||||
tkns []caddyfile.Token,
|
// for a cleaner overall output.
|
||||||
matcherDefs map[string]map[string]json.RawMessage,
|
func consolidateAutomationPolicies(aps []caddytls.AutomationPolicy) []caddytls.AutomationPolicy {
|
||||||
warnings *[]caddyconfig.Warning,
|
for i := 0; i < len(aps); i++ {
|
||||||
) (map[string]matcherSetAndTokens, error) {
|
for j := 0; j < len(aps); j++ {
|
||||||
m := make(map[string]matcherSetAndTokens)
|
if j == i {
|
||||||
|
continue
|
||||||
for len(tkns) > 0 {
|
|
||||||
d := caddyfile.NewDispenser("Caddyfile", tkns)
|
|
||||||
d.Next() // consume directive token
|
|
||||||
|
|
||||||
// look for matcher; it should be the next argument
|
|
||||||
var matcherToken caddyfile.Token
|
|
||||||
var matcherSet map[string]json.RawMessage
|
|
||||||
if d.NextArg() {
|
|
||||||
var ok bool
|
|
||||||
var err error
|
|
||||||
matcherSet, ok, err = st.matcherSetFromMatcherToken(d.Token(), matcherDefs, warnings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if ok {
|
if reflect.DeepEqual(aps[i].ManagementRaw, aps[j].ManagementRaw) {
|
||||||
// found a matcher; save it, then splice it out
|
aps[i].Hosts = append(aps[i].Hosts, aps[j].Hosts...)
|
||||||
// since we don't want to parse it again
|
|
||||||
matcherToken = d.Token()
|
|
||||||
tkns = d.Delete()
|
|
||||||
}
|
}
|
||||||
d.RemainingArgs() // advance to end of line
|
aps = append(aps[:j], aps[j+1:]...)
|
||||||
}
|
i--
|
||||||
for d.NextBlock() {
|
break
|
||||||
// skip entire block including any nested blocks; all
|
|
||||||
// we care about is accessing next directive occurrence
|
|
||||||
for d.Nested() {
|
|
||||||
d.NextBlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end := d.Cursor() + 1
|
return aps
|
||||||
m[matcherToken.Text] = matcherSetAndTokens{
|
|
||||||
matcherSet: matcherSet,
|
|
||||||
tokens: append(m[matcherToken.Text].tokens, tkns[:end]...),
|
|
||||||
}
|
|
||||||
tkns = tkns[end:]
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ServerType) matcherSetFromMatcherToken(
|
func matcherSetFromMatcherToken(
|
||||||
tkn caddyfile.Token,
|
tkn caddyfile.Token,
|
||||||
matcherDefs map[string]map[string]json.RawMessage,
|
matcherDefs map[string]map[string]json.RawMessage,
|
||||||
warnings *[]caddyconfig.Warning,
|
warnings *[]caddyconfig.Warning,
|
||||||
|
@ -424,10 +406,11 @@ func (st *ServerType) compileEncodedMatcherSets(sblock caddyfile.ServerBlock) ([
|
||||||
var matcherPairs []*hostPathPair
|
var matcherPairs []*hostPathPair
|
||||||
|
|
||||||
for _, key := range sblock.Keys {
|
for _, key := range sblock.Keys {
|
||||||
addr, err := standardizeAddress(key)
|
addr, err := ParseAddress(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
|
return nil, fmt.Errorf("server block %v: parsing and standardizing address '%s': %v", sblock.Keys, key, err)
|
||||||
}
|
}
|
||||||
|
addr = addr.Normalize()
|
||||||
|
|
||||||
// choose a matcher pair that should be shared by this
|
// choose a matcher pair that should be shared by this
|
||||||
// server block; if none exists yet, create one
|
// server block; if none exists yet, create one
|
||||||
|
@ -504,14 +487,6 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (map[string]
|
||||||
return msEncoded, nil
|
return msEncoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerDirective implements a directive for an HTTP handler,
|
|
||||||
// in that it can unmarshal its own configuration from Caddyfile
|
|
||||||
// tokens and also specify which directive bucket it belongs in.
|
|
||||||
type HandlerDirective interface {
|
|
||||||
caddyfile.Unmarshaler
|
|
||||||
Bucket() int
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryInt tries to convert str to an integer. If it fails, it downgrades
|
// tryInt tries to convert str to an integer. If it fails, it downgrades
|
||||||
// the error to a warning and returns 0.
|
// the error to a warning and returns 0.
|
||||||
func tryInt(str string, warnings *[]caddyconfig.Warning) int {
|
func tryInt(str string, warnings *[]caddyconfig.Warning) int {
|
||||||
|
@ -535,7 +510,7 @@ type matcherSetAndTokens struct {
|
||||||
// served on those addresses.
|
// served on those addresses.
|
||||||
type sbAddrAssociation struct {
|
type sbAddrAssociation struct {
|
||||||
addresses []string
|
addresses []string
|
||||||
serverBlocks []caddyfile.ServerBlock
|
serverBlocks []serverBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
|
|
11
context.go
11
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)
|
return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
val := mod.New()
|
val := mod.New().(interface{})
|
||||||
|
|
||||||
// value must be a pointer for unmarshaling into concrete type
|
// value must be a pointer for unmarshaling into concrete type, even if
|
||||||
|
// the module's concrete type is a slice or map; New() *should* return
|
||||||
|
// a pointer, otherwise unmarshaling errors or panics will occur
|
||||||
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
|
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
|
||||||
val = reflect.New(rv.Type()).Elem().Addr().Interface()
|
log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
|
||||||
|
" so we are using reflection to make a pointer instead; please fix this by"+
|
||||||
|
" using new(Type) or &Type notation in your module's New() function.", name)
|
||||||
|
val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill in its config only if there is a config to fill in
|
// fill in its config only if there is a config to fill in
|
||||||
|
|
109
modules.go
109
modules.go
|
@ -23,48 +23,76 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Module represents a Caddy module.
|
// Module is a type that is used as a Caddy module.
|
||||||
type Module struct {
|
type Module interface {
|
||||||
|
// This method indicates the type is a Caddy
|
||||||
|
// module. The returned ModuleInfo must have
|
||||||
|
// both a name and a constructor function.
|
||||||
|
// This method must not have any side-effects.
|
||||||
|
CaddyModule() ModuleInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModuleInfo represents a registered Caddy module.
|
||||||
|
type ModuleInfo struct {
|
||||||
// Name is the full name of the module. It
|
// Name is the full name of the module. It
|
||||||
// must be unique and properly namespaced.
|
// must be unique and properly namespaced.
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// New returns a new, empty instance of
|
// New returns a pointer to a new, empty
|
||||||
// the module's type. The host module
|
// instance of the module's type. The host
|
||||||
// which loads this module will likely
|
// module which instantiates this module will
|
||||||
// invoke methods on the returned value.
|
// likely type-assert and invoke methods on
|
||||||
// It must return a pointer; if not, it
|
// the returned value. This function must not
|
||||||
// is converted into one.
|
// have any side-effects.
|
||||||
New func() interface{}
|
New func() Module
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns a module's ID, which is the
|
|
||||||
// last element of its name.
|
|
||||||
func (m Module) ID() string {
|
|
||||||
if m.Name == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
parts := strings.Split(m.Name, ".")
|
|
||||||
return parts[len(parts)-1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace returns the module's namespace (scope)
|
// Namespace returns the module's namespace (scope)
|
||||||
// which is all but the last element of its name.
|
// which is all but the last element of its name.
|
||||||
func (m Module) Namespace() string {
|
// If there is no explicit namespace in the name,
|
||||||
lastDot := strings.LastIndex(m.Name, ".")
|
// the whole name is considered the namespace.
|
||||||
|
func (mi ModuleInfo) Namespace() string {
|
||||||
|
lastDot := strings.LastIndex(mi.Name, ".")
|
||||||
if lastDot < 0 {
|
if lastDot < 0 {
|
||||||
return ""
|
return mi.Name
|
||||||
}
|
}
|
||||||
return m.Name[:lastDot]
|
return mi.Name[:lastDot]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Module) String() string { return m.Name }
|
// ID returns a module's ID, which is the
|
||||||
|
// last element of its name.
|
||||||
|
func (mi ModuleInfo) ID() string {
|
||||||
|
if mi.Name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.Split(mi.Name, ".")
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterModule registers a module. Modules must call
|
func (mi ModuleInfo) String() string { return mi.Name }
|
||||||
// this function in the init phase of runtime.
|
|
||||||
func RegisterModule(mod Module) error {
|
// RegisterModule registers a module by receiving a
|
||||||
if mod.Name == "caddy" {
|
// plain/empty value of the module. For registration to
|
||||||
return fmt.Errorf("modules cannot be named 'caddy'")
|
// be properly recorded, this should be called in the
|
||||||
|
// init phase of runtime. Typically, the module package
|
||||||
|
// will do this as a side-effect of being imported.
|
||||||
|
// This function returns an error if the module's info
|
||||||
|
// is incomplete or invalid, or if the module is
|
||||||
|
// already registered.
|
||||||
|
func RegisterModule(instance Module) error {
|
||||||
|
mod := instance.CaddyModule()
|
||||||
|
|
||||||
|
if mod.Name == "" {
|
||||||
|
return fmt.Errorf("missing ModuleInfo.Name")
|
||||||
|
}
|
||||||
|
if mod.Name == "caddy" || mod.Name == "admin" {
|
||||||
|
return fmt.Errorf("module name '%s' is reserved", mod.Name)
|
||||||
|
}
|
||||||
|
if mod.New == nil {
|
||||||
|
return fmt.Errorf("missing ModuleInfo.New")
|
||||||
|
}
|
||||||
|
if val := mod.New(); val == nil {
|
||||||
|
return fmt.Errorf("ModuleInfo.New must return a non-nil module instance")
|
||||||
}
|
}
|
||||||
|
|
||||||
modulesMu.Lock()
|
modulesMu.Lock()
|
||||||
|
@ -77,18 +105,27 @@ func RegisterModule(mod Module) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModule returns a module by its full name.
|
// GetModule returns module information from its full name.
|
||||||
func GetModule(name string) (Module, error) {
|
func GetModule(name string) (ModuleInfo, error) {
|
||||||
modulesMu.Lock()
|
modulesMu.Lock()
|
||||||
defer modulesMu.Unlock()
|
defer modulesMu.Unlock()
|
||||||
|
|
||||||
m, ok := modules[name]
|
m, ok := modules[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return Module{}, fmt.Errorf("module not registered: %s", name)
|
return ModuleInfo{}, fmt.Errorf("module not registered: %s", name)
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetModuleName returns a module's name from an instance of its value.
|
||||||
|
// If the value is not a module, an empty name will be returned.
|
||||||
|
func GetModuleName(instance interface{}) string {
|
||||||
|
var name string
|
||||||
|
if mod, ok := instance.(Module); ok {
|
||||||
|
name = mod.CaddyModule().Name
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
// GetModules returns all modules in the given scope/namespace.
|
// GetModules returns all modules in the given scope/namespace.
|
||||||
// For example, a scope of "foo" returns modules named "foo.bar",
|
// For example, a scope of "foo" returns modules named "foo.bar",
|
||||||
// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope
|
// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope
|
||||||
|
@ -98,7 +135,7 @@ func GetModule(name string) (Module, error) {
|
||||||
//
|
//
|
||||||
// Because modules are registered to a map, the returned slice
|
// Because modules are registered to a map, the returned slice
|
||||||
// will be sorted to keep it deterministic.
|
// will be sorted to keep it deterministic.
|
||||||
func GetModules(scope string) []Module {
|
func GetModules(scope string) []ModuleInfo {
|
||||||
modulesMu.Lock()
|
modulesMu.Lock()
|
||||||
defer modulesMu.Unlock()
|
defer modulesMu.Unlock()
|
||||||
|
|
||||||
|
@ -110,7 +147,7 @@ func GetModules(scope string) []Module {
|
||||||
scopeParts = []string{}
|
scopeParts = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mods []Module
|
var mods []ModuleInfo
|
||||||
iterateModules:
|
iterateModules:
|
||||||
for name, m := range modules {
|
for name, m := range modules {
|
||||||
modParts := strings.Split(name, ".")
|
modParts := strings.Split(name, ".")
|
||||||
|
@ -223,6 +260,6 @@ func strictUnmarshalJSON(data []byte, v interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
modules = make(map[string]Module)
|
modules = make(map[string]ModuleInfo)
|
||||||
modulesMu sync.Mutex
|
modulesMu sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,10 +37,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
weakrand.Seed(time.Now().UnixNano())
|
weakrand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
err := caddy.RegisterModule(caddy.Module{
|
err := caddy.RegisterModule(App{})
|
||||||
Name: "http",
|
|
||||||
New: func() interface{} { return new(App) },
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -58,6 +55,14 @@ type App struct {
|
||||||
ctx caddy.Context
|
ctx caddy.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (App) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http",
|
||||||
|
New: func() caddy.Module { return new(App) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision sets up the app.
|
// Provision sets up the app.
|
||||||
func (app *App) Provision(ctx caddy.Context) error {
|
func (app *App) Provision(ctx caddy.Context) error {
|
||||||
app.ctx = ctx
|
app.ctx = ctx
|
||||||
|
@ -227,7 +232,7 @@ func (app *App) automaticHTTPS() error {
|
||||||
// find all qualifying domain names, de-duplicated
|
// find all qualifying domain names, de-duplicated
|
||||||
domainSet := make(map[string]struct{})
|
domainSet := make(map[string]struct{})
|
||||||
for _, route := range srv.Routes {
|
for _, route := range srv.Routes {
|
||||||
for _, matcherSet := range route.matcherSets {
|
for _, matcherSet := range route.MatcherSets {
|
||||||
for _, m := range matcherSet {
|
for _, m := range matcherSet {
|
||||||
if hm, ok := m.(*MatchHost); ok {
|
if hm, ok := m.(*MatchHost); ok {
|
||||||
for _, d := range *hm {
|
for _, d := range *hm {
|
||||||
|
@ -331,13 +336,13 @@ func (app *App) automaticHTTPS() error {
|
||||||
redirTo += "{http.request.uri}"
|
redirTo += "{http.request.uri}"
|
||||||
|
|
||||||
redirRoutes = append(redirRoutes, Route{
|
redirRoutes = append(redirRoutes, Route{
|
||||||
matcherSets: []MatcherSet{
|
MatcherSets: []MatcherSet{
|
||||||
{
|
{
|
||||||
MatchProtocol("http"),
|
MatchProtocol("http"),
|
||||||
MatchHost(domains),
|
MatchHost(domains),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
handlers: []MiddlewareHandler{
|
Handlers: []MiddlewareHandler{
|
||||||
StaticResponse{
|
StaticResponse{
|
||||||
StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
|
StatusCode: WeakString(strconv.Itoa(http.StatusTemporaryRedirect)), // TODO: use permanent redirect instead
|
||||||
Headers: http.Header{
|
Headers: http.Header{
|
||||||
|
|
|
@ -24,10 +24,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Log{})
|
||||||
Name: "http.handlers.log",
|
|
||||||
New: func() interface{} { return new(Log) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log implements a simple logging middleware.
|
// Log implements a simple logging middleware.
|
||||||
|
@ -36,6 +33,14 @@ type Log struct {
|
||||||
counter int
|
counter int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Log) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.log",
|
||||||
|
New: func() caddy.Module { return new(Log) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/andybalholm/brotli"
|
"github.com/andybalholm/brotli"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Brotli{})
|
||||||
Name: "http.encoders.brotli",
|
|
||||||
New: func() interface{} { return new(Brotli) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brotli can create brotli encoders. Note that brotli
|
// Brotli can create brotli encoders. Note that brotli
|
||||||
|
@ -37,6 +34,14 @@ type Brotli struct {
|
||||||
Quality *int `json:"quality,omitempty"`
|
Quality *int `json:"quality,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Brotli) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.encoders.brotli",
|
||||||
|
New: func() caddy.Module { return new(Brotli) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||||
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
|
|
|
@ -22,8 +22,25 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpcaddyfile.RegisterHandlerDirective("encode", parseCaddyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is a good example of why UnmarshalCaddyfile is still a good idea... hmm.
|
||||||
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
|
enc := new(Encode)
|
||||||
|
err := enc.UnmarshalCaddyfile(h.Dispenser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return enc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Keep UnmarshalCaddyfile pattern?
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// encode [<matcher>] <formats...> {
|
// encode [<matcher>] <formats...> {
|
||||||
|
@ -78,8 +95,5 @@ func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (enc Encode) Bucket() int { return 3 }
|
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
var _ httpcaddyfile.HandlerDirective = (*Encode)(nil)
|
var _ caddyfile.Unmarshaler = (*Encode)(nil)
|
||||||
|
|
|
@ -35,10 +35,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Encode{})
|
||||||
Name: "http.handlers.encode",
|
|
||||||
New: func() interface{} { return new(Encode) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode is a middleware which can encode responses.
|
// Encode is a middleware which can encode responses.
|
||||||
|
@ -50,6 +47,14 @@ type Encode struct {
|
||||||
writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
|
writerPools map[string]*sync.Pool // TODO: these pools do not get reused through config reloads...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Encode) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.encode",
|
||||||
|
New: func() caddy.Module { return new(Encode) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision provisions enc.
|
// Provision provisions enc.
|
||||||
func (enc *Encode) Provision(ctx caddy.Context) error {
|
func (enc *Encode) Provision(ctx caddy.Context) error {
|
||||||
for modName, rawMsg := range enc.EncodingsRaw {
|
for modName, rawMsg := range enc.EncodingsRaw {
|
||||||
|
|
|
@ -20,16 +20,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Gzip{})
|
||||||
Name: "http.encoders.gzip",
|
|
||||||
New: func() interface{} { return new(Gzip) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gzip can create gzip encoders.
|
// Gzip can create gzip encoders.
|
||||||
|
@ -37,6 +34,14 @@ type Gzip struct {
|
||||||
Level int `json:"level,omitempty"`
|
Level int `json:"level,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Gzip) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.encoders.gzip",
|
||||||
|
New: func() caddy.Module { return new(Gzip) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||||
func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
|
|
|
@ -15,22 +15,27 @@
|
||||||
package caddyzstd
|
package caddyzstd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Zstd{})
|
||||||
Name: "http.encoders.zstd",
|
|
||||||
New: func() interface{} { return new(Zstd) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zstd can create Zstandard encoders.
|
// Zstd can create Zstandard encoders.
|
||||||
type Zstd struct{}
|
type Zstd struct{}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Zstd) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.encoders.zstd",
|
||||||
|
New: func() caddy.Module { return new(Zstd) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||||
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -15,59 +15,58 @@
|
||||||
package fileserver
|
package fileserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/modules/caddyhttp/rewrite"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
func init() {
|
||||||
//
|
httpcaddyfile.RegisterHandlerDirective("file_server", parseCaddyfile)
|
||||||
// file_server [<matcher>] [browse] {
|
httpcaddyfile.RegisterDirective("try_files", parseTryFiles)
|
||||||
// hide <files...>
|
}
|
||||||
// index <files...>
|
|
||||||
// browse [<template_file>]
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
// root <path>
|
var fsrv FileServer
|
||||||
// }
|
|
||||||
//
|
for h.Next() {
|
||||||
// If browse is given on the first line, it can't be used in the block also.
|
args := h.RemainingArgs()
|
||||||
// The default root is the one given by the root directive.
|
|
||||||
func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|
||||||
for d.Next() {
|
|
||||||
args := d.RemainingArgs()
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
if args[0] != "browse" {
|
if args[0] != "browse" {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
fsrv.Browse = new(Browse)
|
fsrv.Browse = new(Browse)
|
||||||
default:
|
default:
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
for d.NextBlock() {
|
for h.NextBlock() {
|
||||||
switch d.Val() {
|
switch h.Val() {
|
||||||
case "hide":
|
case "hide":
|
||||||
fsrv.Hide = d.RemainingArgs()
|
fsrv.Hide = h.RemainingArgs()
|
||||||
if len(fsrv.Hide) == 0 {
|
if len(fsrv.Hide) == 0 {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
case "index":
|
case "index":
|
||||||
fsrv.IndexNames = d.RemainingArgs()
|
fsrv.IndexNames = h.RemainingArgs()
|
||||||
if len(fsrv.Hide) == 0 {
|
if len(fsrv.Hide) == 0 {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
case "root":
|
case "root":
|
||||||
if !d.Args(&fsrv.Root) {
|
if !h.Args(&fsrv.Root) {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
case "browse":
|
case "browse":
|
||||||
if fsrv.Browse != nil {
|
if fsrv.Browse != nil {
|
||||||
return d.Err("browsing is already configured")
|
return nil, h.Err("browsing is already configured")
|
||||||
}
|
}
|
||||||
fsrv.Browse = new(Browse)
|
fsrv.Browse = new(Browse)
|
||||||
d.Args(&fsrv.Browse.TemplateFile)
|
h.Args(&fsrv.Browse.TemplateFile)
|
||||||
default:
|
default:
|
||||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
return nil, h.Errf("unknown subdirective '%s'", h.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,11 +76,29 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
fsrv.Root = "{http.var.root}"
|
fsrv.Root = "{http.var.root}"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return &fsrv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||||
func (fsrv FileServer) Bucket() int { return 7 }
|
if !h.Next() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guard
|
try := h.RemainingArgs()
|
||||||
var _ httpcaddyfile.HandlerDirective = (*FileServer)(nil)
|
if len(try) == 0 {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := rewrite.Rewrite{
|
||||||
|
URI: "{http.matchers.file.relative}{http.request.uri.query}",
|
||||||
|
}
|
||||||
|
|
||||||
|
matcherSet := map[string]json.RawMessage{
|
||||||
|
"file": h.JSON(MatchFile{
|
||||||
|
Root: "{http.var.root}",
|
||||||
|
TryFiles: try,
|
||||||
|
}, nil),
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.NewRoute(matcherSet, handler), nil
|
||||||
|
}
|
||||||
|
|
|
@ -20,16 +20,13 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(MatchFile{})
|
||||||
Name: "http.matchers.file",
|
|
||||||
New: func() interface{} { return new(MatchFile) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchFile is an HTTP request matcher that can match
|
// MatchFile is an HTTP request matcher that can match
|
||||||
|
@ -52,12 +49,20 @@ type MatchFile struct {
|
||||||
TryPolicy string `json:"try_policy,omitempty"`
|
TryPolicy string `json:"try_policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchFile) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.file",
|
||||||
|
New: func() caddy.Module { return new(MatchFile) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax:
|
// UnmarshalCaddyfile sets up the matcher from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// file {
|
// file {
|
||||||
// root <path>
|
// root <path>
|
||||||
// try_files <files...>
|
// try_files <files...>
|
||||||
// try_policy <first_exist|smallest_size|largest_size|most_recent_modified>
|
// try_policy first_exist|smallest_size|largest_size|most_recent_modified
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
@ -82,6 +87,9 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if m.Root == "" {
|
||||||
|
m.Root = "{http.var.root}"
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +129,7 @@ func (m MatchFile) Match(r *http.Request) bool {
|
||||||
func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
|
||||||
root := repl.ReplaceAll(m.Root, "")
|
root := repl.ReplaceAll(m.Root, ".")
|
||||||
|
|
||||||
// if list of files to try was omitted entirely,
|
// if list of files to try was omitted entirely,
|
||||||
// assume URL path
|
// assume URL path
|
||||||
|
|
|
@ -36,10 +36,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
weakrand.Seed(time.Now().UnixNano())
|
weakrand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(FileServer{})
|
||||||
Name: "http.handlers.file_server",
|
|
||||||
New: func() interface{} { return new(FileServer) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileServer implements a static file server responder for Caddy.
|
// FileServer implements a static file server responder for Caddy.
|
||||||
|
@ -50,6 +47,14 @@ type FileServer struct {
|
||||||
Browse *Browse `json:"browse,omitempty"`
|
Browse *Browse `json:"browse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (FileServer) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.file_server",
|
||||||
|
New: func() caddy.Module { return new(FileServer) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision sets up the static files responder.
|
// Provision sets up the static files responder.
|
||||||
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
|
func (fsrv *FileServer) Provision(ctx caddy.Context) error {
|
||||||
if fsrv.IndexNames == nil {
|
if fsrv.IndexNames == nil {
|
||||||
|
|
|
@ -18,11 +18,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
func init() {
|
||||||
|
httpcaddyfile.RegisterHandlerDirective("headers", parseCaddyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// headers [<matcher>] [[+|-]<field> <value>] {
|
// headers [<matcher>] [[+|-]<field> <value>] {
|
||||||
// [+][<field>] [<value>]
|
// [+][<field>] [<value>]
|
||||||
|
@ -31,62 +35,57 @@ import (
|
||||||
//
|
//
|
||||||
// Either a block can be opened or a single header field can be configured
|
// Either a block can be opened or a single header field can be configured
|
||||||
// in the first line, but not both in the same directive.
|
// in the first line, but not both in the same directive.
|
||||||
func (h *Headers) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
for d.Next() {
|
hdr := new(Headers)
|
||||||
|
for h.Next() {
|
||||||
// first see if headers are in the initial line
|
// first see if headers are in the initial line
|
||||||
var hasArgs bool
|
var hasArgs bool
|
||||||
if d.NextArg() {
|
if h.NextArg() {
|
||||||
hasArgs = true
|
hasArgs = true
|
||||||
field := d.Val()
|
field := h.Val()
|
||||||
d.NextArg()
|
h.NextArg()
|
||||||
value := d.Val()
|
value := h.Val()
|
||||||
h.processCaddyfileLine(field, value)
|
processCaddyfileLine(hdr, field, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not, they should be in a block
|
// if not, they should be in a block
|
||||||
for d.NextBlock() {
|
for h.NextBlock() {
|
||||||
if hasArgs {
|
if hasArgs {
|
||||||
return d.Err("cannot specify headers in both arguments and block")
|
return nil, h.Err("cannot specify headers in both arguments and block")
|
||||||
}
|
}
|
||||||
field := d.Val()
|
field := h.Val()
|
||||||
var value string
|
var value string
|
||||||
if d.NextArg() {
|
if h.NextArg() {
|
||||||
value = d.Val()
|
value = h.Val()
|
||||||
}
|
}
|
||||||
h.processCaddyfileLine(field, value)
|
processCaddyfileLine(hdr, field, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return hdr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headers) processCaddyfileLine(field, value string) {
|
func processCaddyfileLine(hdr *Headers, field, value string) {
|
||||||
if strings.HasPrefix(field, "+") {
|
if strings.HasPrefix(field, "+") {
|
||||||
if h.Response == nil {
|
if hdr.Response == nil {
|
||||||
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
||||||
}
|
}
|
||||||
if h.Response.Add == nil {
|
if hdr.Response.Add == nil {
|
||||||
h.Response.Add = make(http.Header)
|
hdr.Response.Add = make(http.Header)
|
||||||
}
|
}
|
||||||
h.Response.Add.Set(field[1:], value)
|
hdr.Response.Add.Set(field[1:], value)
|
||||||
} else if strings.HasPrefix(field, "-") {
|
} else if strings.HasPrefix(field, "-") {
|
||||||
if h.Response == nil {
|
if hdr.Response == nil {
|
||||||
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
||||||
}
|
}
|
||||||
h.Response.Delete = append(h.Response.Delete, field[1:])
|
hdr.Response.Delete = append(hdr.Response.Delete, field[1:])
|
||||||
h.Response.Deferred = true
|
hdr.Response.Deferred = true
|
||||||
} else {
|
} else {
|
||||||
if h.Response == nil {
|
if hdr.Response == nil {
|
||||||
h.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
hdr.Response = &RespHeaderOps{HeaderOps: new(HeaderOps)}
|
||||||
}
|
}
|
||||||
if h.Response.Set == nil {
|
if hdr.Response.Set == nil {
|
||||||
h.Response.Set = make(http.Header)
|
hdr.Response.Set = make(http.Header)
|
||||||
}
|
}
|
||||||
h.Response.Set.Set(field, value)
|
hdr.Response.Set.Set(field, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (h Headers) Bucket() int { return 3 }
|
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ httpcaddyfile.HandlerDirective = (*Headers)(nil)
|
|
||||||
|
|
|
@ -23,10 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Headers{})
|
||||||
Name: "http.handlers.headers",
|
|
||||||
New: func() interface{} { return new(Headers) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers is a middleware which can mutate HTTP headers.
|
// Headers is a middleware which can mutate HTTP headers.
|
||||||
|
@ -35,6 +32,14 @@ type Headers struct {
|
||||||
Response *RespHeaderOps `json:"response,omitempty"`
|
Response *RespHeaderOps `json:"response,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Headers) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.headers",
|
||||||
|
New: func() caddy.Module { return new(Headers) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HeaderOps defines some operations to
|
// HeaderOps defines some operations to
|
||||||
// perform on HTTP headers.
|
// perform on HTTP headers.
|
||||||
type HeaderOps struct {
|
type HeaderOps struct {
|
||||||
|
|
|
@ -28,16 +28,21 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Markdown{})
|
||||||
Name: "http.handlers.markdown",
|
|
||||||
New: func() interface{} { return new(Markdown) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Markdown is a middleware for rendering a Markdown response body.
|
// Markdown is a middleware for rendering a Markdown response body.
|
||||||
type Markdown struct {
|
type Markdown struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Markdown) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.markdown",
|
||||||
|
New: func() caddy.Module { return new(Markdown) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (m Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
|
|
@ -80,50 +80,25 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(MatchHost{})
|
||||||
|
caddy.RegisterModule(MatchPath{})
|
||||||
|
caddy.RegisterModule(MatchPathRE{})
|
||||||
|
caddy.RegisterModule(MatchMethod{})
|
||||||
|
caddy.RegisterModule(MatchQuery{})
|
||||||
|
caddy.RegisterModule(MatchHeader{})
|
||||||
|
caddy.RegisterModule(MatchHeaderRE{})
|
||||||
|
caddy.RegisterModule(new(MatchProtocol))
|
||||||
|
caddy.RegisterModule(MatchRemoteIP{})
|
||||||
|
caddy.RegisterModule(MatchNegate{})
|
||||||
|
caddy.RegisterModule(new(MatchStarlarkExpr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchHost) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
Name: "http.matchers.host",
|
Name: "http.matchers.host",
|
||||||
New: func() interface{} { return new(MatchHost) },
|
New: func() caddy.Module { return new(MatchHost) },
|
||||||
})
|
}
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.path",
|
|
||||||
New: func() interface{} { return new(MatchPath) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.path_regexp",
|
|
||||||
New: func() interface{} { return new(MatchPathRE) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.method",
|
|
||||||
New: func() interface{} { return new(MatchMethod) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.query",
|
|
||||||
New: func() interface{} { return new(MatchQuery) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.header",
|
|
||||||
New: func() interface{} { return new(MatchHeader) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.header_regexp",
|
|
||||||
New: func() interface{} { return new(MatchHeaderRE) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.protocol",
|
|
||||||
New: func() interface{} { return new(MatchProtocol) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.remote_ip",
|
|
||||||
New: func() interface{} { return new(MatchRemoteIP) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.not",
|
|
||||||
New: func() interface{} { return new(MatchNegate) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.starlark_expr",
|
|
||||||
New: func() interface{} { return new(MatchStarlarkExpr) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
|
@ -165,6 +140,14 @@ outer:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchPath) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.path",
|
||||||
|
New: func() caddy.Module { return new(MatchPath) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPath) Match(r *http.Request) bool {
|
func (m MatchPath) Match(r *http.Request) bool {
|
||||||
for _, matchPath := range m {
|
for _, matchPath := range m {
|
||||||
|
@ -186,19 +169,39 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
*m = d.RemainingArgs()
|
*m = d.RemainingArgs()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.path_regexp",
|
||||||
|
New: func() caddy.Module { return new(MatchPathRE) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchPathRE) Match(r *http.Request) bool {
|
func (m MatchPathRE) Match(r *http.Request) bool {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
|
return m.MatchRegexp.Match(r.URL.Path, repl, "path_regexp")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchMethod) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.method",
|
||||||
|
New: func() caddy.Module { return new(MatchMethod) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
*m = d.RemainingArgs()
|
*m = d.RemainingArgs()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +215,14 @@ func (m MatchMethod) Match(r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchQuery) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.query",
|
||||||
|
New: func() caddy.Module { return new(MatchQuery) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
|
@ -237,6 +248,14 @@ func (m MatchQuery) Match(r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchHeader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.header",
|
||||||
|
New: func() caddy.Module { return new(MatchHeader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
|
@ -270,6 +289,14 @@ func (m MatchHeader) Match(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.header_regexp",
|
||||||
|
New: func() caddy.Module { return new(MatchHeaderRE) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
if *m == nil {
|
if *m == nil {
|
||||||
|
@ -319,6 +346,14 @@ func (m MatchHeaderRE) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.protocol",
|
||||||
|
New: func() caddy.Module { return new(MatchProtocol) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchProtocol) Match(r *http.Request) bool {
|
func (m MatchProtocol) Match(r *http.Request) bool {
|
||||||
switch string(m) {
|
switch string(m) {
|
||||||
|
@ -334,14 +369,24 @@ func (m MatchProtocol) Match(r *http.Request) bool {
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
var proto string
|
var proto string
|
||||||
if !d.Args(&proto) {
|
if !d.Args(&proto) {
|
||||||
return d.Err("expected exactly one protocol")
|
return d.Err("expected exactly one protocol")
|
||||||
}
|
}
|
||||||
*m = MatchProtocol(proto)
|
*m = MatchProtocol(proto)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchNegate) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.not",
|
||||||
|
New: func() caddy.Module { return new(MatchNegate) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals data into m's unexported map field.
|
// UnmarshalJSON unmarshals data into m's unexported map field.
|
||||||
// This is done because we cannot embed the map directly into
|
// This is done because we cannot embed the map directly into
|
||||||
// the struct, but we need a struct because we need another
|
// the struct, but we need a struct because we need another
|
||||||
|
@ -375,9 +420,19 @@ func (m MatchNegate) Match(r *http.Request) bool {
|
||||||
return !m.matchers.Match(r)
|
return !m.matchers.Match(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.remote_ip",
|
||||||
|
New: func() caddy.Module { return new(MatchRemoteIP) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
for d.Next() {
|
||||||
m.Ranges = d.RemainingArgs()
|
m.Ranges = d.RemainingArgs()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +497,14 @@ func (m MatchRemoteIP) Match(r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchStarlarkExpr) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.starlark_expr", // TODO: Rename to 'starlark'?
|
||||||
|
New: func() caddy.Module { return new(MatchStarlarkExpr) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
|
func (m MatchStarlarkExpr) Match(r *http.Request) bool {
|
||||||
input := string(m)
|
input := string(m)
|
||||||
|
@ -513,8 +576,17 @@ func (mre *MatchRegexp) Match(input string, repl caddy.Replacer, scope string) b
|
||||||
|
|
||||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||||
func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
if !d.Args(&mre.Name, &mre.Pattern) {
|
for d.Next() {
|
||||||
return fmt.Errorf("missing arguments")
|
args := d.RemainingArgs()
|
||||||
|
switch len(args) {
|
||||||
|
case 1:
|
||||||
|
mre.Pattern = args[0]
|
||||||
|
case 2:
|
||||||
|
mre.Name = args[0]
|
||||||
|
mre.Pattern = args[1]
|
||||||
|
default:
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(RequestBody{})
|
||||||
Name: "http.handlers.request_body",
|
|
||||||
New: func() interface{} { return new(RequestBody) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestBody is a middleware for manipulating the request body.
|
// RequestBody is a middleware for manipulating the request body.
|
||||||
|
@ -33,6 +30,14 @@ type RequestBody struct {
|
||||||
MaxSize int64 `json:"max_size,omitempty"`
|
MaxSize int64 `json:"max_size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (RequestBody) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.request_body", // TODO: better name for this?
|
||||||
|
New: func() caddy.Module { return new(RequestBody) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return next.ServeHTTP(w, r)
|
return next.ServeHTTP(w, r)
|
||||||
|
|
|
@ -15,39 +15,39 @@
|
||||||
package reverseproxy
|
package reverseproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register caddy module.
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(new(LoadBalanced))
|
||||||
Name: "http.handlers.reverse_proxy",
|
httpcaddyfile.RegisterHandlerDirective("reverse_proxy", parseCaddyfile) // TODO: "proxy"?
|
||||||
New: func() interface{} { return new(LoadBalanced) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (*LoadBalanced) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.reverse_proxy",
|
||||||
|
New: func() caddy.Module { return new(LoadBalanced) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// proxy [<matcher>] <to>
|
// proxy [<matcher>] <to>
|
||||||
//
|
//
|
||||||
// TODO: This needs to be finished. It definitely needs to be able to open a block...
|
// TODO: This needs to be finished. It definitely needs to be able to open a block...
|
||||||
func (lb *LoadBalanced) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
for d.Next() {
|
lb := new(LoadBalanced)
|
||||||
allTo := d.RemainingArgs()
|
for h.Next() {
|
||||||
|
allTo := h.RemainingArgs()
|
||||||
if len(allTo) == 0 {
|
if len(allTo) == 0 {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
for _, to := range allTo {
|
for _, to := range allTo {
|
||||||
lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to})
|
lb.Upstreams = append(lb.Upstreams, &UpstreamConfig{Host: to})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return lb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (*LoadBalanced) Bucket() int { return 7 }
|
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ httpcaddyfile.HandlerDirective = (*LoadBalanced)(nil)
|
|
||||||
|
|
|
@ -15,24 +15,23 @@
|
||||||
package rewrite
|
package rewrite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
func init() {
|
||||||
|
httpcaddyfile.RegisterHandlerDirective("rewrite", parseCaddyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// rewrite [<matcher>] <to>
|
// rewrite [<matcher>] <to>
|
||||||
//
|
//
|
||||||
// The <to> parameter becomes the new URI.
|
// The <to> parameter becomes the new URI.
|
||||||
func (rewr *Rewrite) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
for d.Next() {
|
var rewr Rewrite
|
||||||
rewr.URI = d.Val()
|
for h.Next() {
|
||||||
|
rewr.URI = h.Val()
|
||||||
}
|
}
|
||||||
return nil
|
return rewr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (rewr Rewrite) Bucket() int { return 1 }
|
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ httpcaddyfile.HandlerDirective = (*Rewrite)(nil)
|
|
||||||
|
|
|
@ -24,10 +24,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Rewrite{})
|
||||||
Name: "http.handlers.rewrite",
|
|
||||||
New: func() interface{} { return new(Rewrite) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite is a middleware which can rewrite HTTP requests.
|
// Rewrite is a middleware which can rewrite HTTP requests.
|
||||||
|
@ -37,6 +34,14 @@ type Rewrite struct {
|
||||||
Rehandle bool `json:"rehandle,omitempty"`
|
Rehandle bool `json:"rehandle,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Rewrite) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.rewrite",
|
||||||
|
New: func() caddy.Module { return new(Rewrite) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
var rehandleNeeded bool
|
var rehandleNeeded bool
|
||||||
|
|
|
@ -27,32 +27,33 @@ import (
|
||||||
// requests.
|
// requests.
|
||||||
type Route struct {
|
type Route struct {
|
||||||
Group string `json:"group,omitempty"`
|
Group string `json:"group,omitempty"`
|
||||||
MatcherSets []map[string]json.RawMessage `json:"match,omitempty"`
|
MatcherSetsRaw []map[string]json.RawMessage `json:"match,omitempty"`
|
||||||
Handle []json.RawMessage `json:"handle,omitempty"`
|
HandlersRaw []json.RawMessage `json:"handle,omitempty"`
|
||||||
Terminal bool `json:"terminal,omitempty"`
|
Terminal bool `json:"terminal,omitempty"`
|
||||||
|
|
||||||
// decoded values
|
// decoded values
|
||||||
matcherSets []MatcherSet
|
MatcherSets []MatcherSet `json:"-"`
|
||||||
handlers []MiddlewareHandler
|
Handlers []MiddlewareHandler `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if the route has all zero/default values.
|
// Empty returns true if the route has all zero/default values.
|
||||||
func (r Route) Empty() bool {
|
func (r Route) Empty() bool {
|
||||||
return len(r.MatcherSets) == 0 &&
|
return len(r.MatcherSetsRaw) == 0 &&
|
||||||
len(r.Handle) == 0 &&
|
len(r.MatcherSets) == 0 &&
|
||||||
len(r.handlers) == 0 &&
|
len(r.HandlersRaw) == 0 &&
|
||||||
|
len(r.Handlers) == 0 &&
|
||||||
!r.Terminal &&
|
!r.Terminal &&
|
||||||
r.Group == ""
|
r.Group == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Route) anyMatcherSetMatches(req *http.Request) bool {
|
func (r Route) anyMatcherSetMatches(req *http.Request) bool {
|
||||||
for _, ms := range r.matcherSets {
|
for _, ms := range r.MatcherSets {
|
||||||
if ms.Match(req) {
|
if ms.Match(req) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if no matchers, always match
|
// if no matchers, always match
|
||||||
return len(r.matcherSets) == 0
|
return len(r.MatcherSets) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatcherSet is a set of matchers which
|
// MatcherSet is a set of matchers which
|
||||||
|
@ -79,7 +80,7 @@ type RouteList []Route
|
||||||
func (routes RouteList) Provision(ctx caddy.Context) error {
|
func (routes RouteList) Provision(ctx caddy.Context) error {
|
||||||
for i, route := range routes {
|
for i, route := range routes {
|
||||||
// matchers
|
// matchers
|
||||||
for _, matcherSet := range route.MatcherSets {
|
for _, matcherSet := range route.MatcherSetsRaw {
|
||||||
var matchers MatcherSet
|
var matchers MatcherSet
|
||||||
for modName, rawMsg := range matcherSet {
|
for modName, rawMsg := range matcherSet {
|
||||||
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
|
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
|
||||||
|
@ -88,19 +89,19 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
matchers = append(matchers, val.(RequestMatcher))
|
matchers = append(matchers, val.(RequestMatcher))
|
||||||
}
|
}
|
||||||
routes[i].matcherSets = append(routes[i].matcherSets, matchers)
|
routes[i].MatcherSets = append(routes[i].MatcherSets, matchers)
|
||||||
}
|
}
|
||||||
routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help?
|
routes[i].MatcherSetsRaw = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
|
|
||||||
// handlers
|
// handlers
|
||||||
for j, rawMsg := range route.Handle {
|
for j, rawMsg := range route.HandlersRaw {
|
||||||
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
|
mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading handler module in position %d: %v", j, err)
|
return fmt.Errorf("loading handler module in position %d: %v", j, err)
|
||||||
}
|
}
|
||||||
routes[i].handlers = append(routes[i].handlers, mh.(MiddlewareHandler))
|
routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler))
|
||||||
}
|
}
|
||||||
routes[i].Handle = nil // allow GC to deallocate - TODO: Does this help?
|
routes[i].HandlersRaw = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -135,7 +136,7 @@ func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the rest of the route
|
// apply the rest of the route
|
||||||
for _, mh := range route.handlers {
|
for _, mh := range route.Handlers {
|
||||||
// we have to be sure to wrap mh outside
|
// we have to be sure to wrap mh outside
|
||||||
// of our current stack frame so that the
|
// of our current stack frame so that the
|
||||||
// reference to this mh isn't overwritten
|
// reference to this mh isn't overwritten
|
||||||
|
|
|
@ -23,10 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(StaticError{})
|
||||||
Name: "http.handlers.error",
|
|
||||||
New: func() interface{} { return new(StaticError) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticError implements a simple handler that returns an error.
|
// StaticError implements a simple handler that returns an error.
|
||||||
|
@ -35,6 +32,14 @@ type StaticError struct {
|
||||||
StatusCode WeakString `json:"status_code,omitempty"`
|
StatusCode WeakString `json:"status_code,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (StaticError) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.error",
|
||||||
|
New: func() caddy.Module { return new(StaticError) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(StaticResponse{})
|
||||||
Name: "http.handlers.static_response",
|
// TODO: Caddyfile directive
|
||||||
New: func() interface{} { return new(StaticResponse) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticResponse implements a simple responder for static responses.
|
// StaticResponse implements a simple responder for static responses.
|
||||||
|
@ -38,6 +36,14 @@ type StaticResponse struct {
|
||||||
Close bool `json:"close,omitempty"`
|
Close bool `json:"close,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (StaticResponse) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.static_response",
|
||||||
|
New: func() caddy.Module { return new(StaticResponse) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// static_response [<matcher>] <status> {
|
// static_response [<matcher>] <status> {
|
||||||
|
@ -71,9 +77,6 @@ func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (StaticResponse) Bucket() int { return 7 }
|
|
||||||
|
|
||||||
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Subroute{})
|
||||||
Name: "http.handlers.subroute",
|
|
||||||
New: func() interface{} { return new(Subroute) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subroute implements a handler that compiles and executes routes.
|
// Subroute implements a handler that compiles and executes routes.
|
||||||
|
@ -37,6 +34,14 @@ type Subroute struct {
|
||||||
Routes RouteList `json:"routes,omitempty"`
|
Routes RouteList `json:"routes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Subroute) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.subroute",
|
||||||
|
New: func() caddy.Module { return new(Subroute) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision sets up subrouting.
|
// Provision sets up subrouting.
|
||||||
func (sr *Subroute) Provision(ctx caddy.Context) error {
|
func (sr *Subroute) Provision(ctx caddy.Context) error {
|
||||||
if sr.Routes != nil {
|
if sr.Routes != nil {
|
||||||
|
|
|
@ -15,11 +15,15 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
func init() {
|
||||||
|
httpcaddyfile.RegisterHandlerDirective("templates", parseCaddyfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||||
//
|
//
|
||||||
// templates [<matcher>] {
|
// templates [<matcher>] {
|
||||||
// mime <types...>
|
// mime <types...>
|
||||||
|
@ -27,23 +31,24 @@ import (
|
||||||
// root <path>
|
// root <path>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||||
for d.Next() {
|
t := new(Templates)
|
||||||
for d.NextBlock() {
|
for h.Next() {
|
||||||
switch d.Val() {
|
for h.NextBlock() {
|
||||||
|
switch h.Val() {
|
||||||
case "mime":
|
case "mime":
|
||||||
t.MIMETypes = d.RemainingArgs()
|
t.MIMETypes = h.RemainingArgs()
|
||||||
if len(t.MIMETypes) == 0 {
|
if len(t.MIMETypes) == 0 {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
case "between":
|
case "between":
|
||||||
t.Delimiters = d.RemainingArgs()
|
t.Delimiters = h.RemainingArgs()
|
||||||
if len(t.Delimiters) != 2 {
|
if len(t.Delimiters) != 2 {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
case "root":
|
case "root":
|
||||||
if !d.Args(&t.IncludeRoot) {
|
if !h.Args(&t.IncludeRoot) {
|
||||||
return d.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,11 +58,5 @@ func (t *Templates) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
t.IncludeRoot = "{http.var.root}"
|
t.IncludeRoot = "{http.var.root}"
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket returns the HTTP Caddyfile handler bucket number.
|
|
||||||
func (t Templates) Bucket() int { return 5 }
|
|
||||||
|
|
||||||
// Interface guard
|
|
||||||
var _ httpcaddyfile.HandlerDirective = (*Templates)(nil)
|
|
||||||
|
|
|
@ -27,10 +27,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(Templates{})
|
||||||
Name: "http.handlers.templates",
|
|
||||||
New: func() interface{} { return new(Templates) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Templates is a middleware which execute response bodies as templates.
|
// Templates is a middleware which execute response bodies as templates.
|
||||||
|
@ -40,6 +37,14 @@ type Templates struct {
|
||||||
Delimiters []string `json:"delimiters,omitempty"`
|
Delimiters []string `json:"delimiters,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (Templates) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.templates",
|
||||||
|
New: func() caddy.Module { return new(Templates) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision provisions t.
|
// Provision provisions t.
|
||||||
func (t *Templates) Provision(ctx caddy.Context) error {
|
func (t *Templates) Provision(ctx caddy.Context) error {
|
||||||
if t.MIMETypes == nil {
|
if t.MIMETypes == nil {
|
||||||
|
|
|
@ -21,20 +21,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(VarsMiddleware{})
|
||||||
Name: "http.handlers.vars",
|
caddy.RegisterModule(VarsMatcher{})
|
||||||
New: func() interface{} { return new(VarsMiddleware) },
|
|
||||||
})
|
|
||||||
caddy.RegisterModule(caddy.Module{
|
|
||||||
Name: "http.matchers.vars",
|
|
||||||
New: func() interface{} { return new(VarsMiddleware) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarsMiddleware is an HTTP middleware which sets variables
|
// VarsMiddleware is an HTTP middleware which sets variables
|
||||||
// in the context, mainly for use by placeholders.
|
// in the context, mainly for use by placeholders.
|
||||||
type VarsMiddleware map[string]string
|
type VarsMiddleware map[string]string
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (VarsMiddleware) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.handlers.vars",
|
||||||
|
New: func() caddy.Module { return new(VarsMiddleware) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
|
func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
|
||||||
vars := r.Context().Value(VarCtxKey).(map[string]interface{})
|
vars := r.Context().Value(VarCtxKey).(map[string]interface{})
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(caddy.Replacer)
|
||||||
|
@ -50,6 +52,14 @@ func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
|
||||||
// requests based on variables in the context.
|
// requests based on variables in the context.
|
||||||
type VarsMatcher map[string]string
|
type VarsMatcher map[string]string
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "http.matchers.vars",
|
||||||
|
New: func() caddy.Module { return new(VarsMatcher) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match matches a request based on variables in the context.
|
// Match matches a request based on variables in the context.
|
||||||
func (m VarsMatcher) Match(r *http.Request) bool {
|
func (m VarsMatcher) Match(r *http.Request) bool {
|
||||||
vars := r.Context().Value(VarCtxKey).(map[string]string)
|
vars := r.Context().Value(VarCtxKey).(map[string]string)
|
||||||
|
|
|
@ -28,10 +28,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(ACMEManagerMaker{})
|
||||||
Name: "tls.management.acme",
|
|
||||||
New: func() interface{} { return new(ACMEManagerMaker) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACMEManagerMaker makes an ACME manager
|
// ACMEManagerMaker makes an ACME manager
|
||||||
|
@ -57,9 +54,17 @@ type ACMEManagerMaker struct {
|
||||||
keyType certcrypto.KeyType
|
keyType certcrypto.KeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
// newManager is a no-op to satisfy the ManagerMaker interface,
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (ACMEManagerMaker) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "tls.management.acme",
|
||||||
|
New: func() caddy.Module { return new(ACMEManagerMaker) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager is a no-op to satisfy the ManagerMaker interface,
|
||||||
// because this manager type is a special case.
|
// because this manager type is a special case.
|
||||||
func (m *ACMEManagerMaker) newManager(interactive bool) (certmagic.Manager, error) {
|
func (m ACMEManagerMaker) NewManager(interactive bool) (certmagic.Manager, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,4 +208,4 @@ func onDemandAskRequest(ask string, name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
var _ managerMaker = (*ACMEManagerMaker)(nil)
|
var _ ManagerMaker = (*ACMEManagerMaker)(nil)
|
||||||
|
|
|
@ -23,15 +23,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(FileLoader{})
|
||||||
Name: "tls.certificates.load_files",
|
|
||||||
New: func() interface{} { return FileLoader{} },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileLoader loads certificates and their associated keys from disk.
|
// FileLoader loads certificates and their associated keys from disk.
|
||||||
type FileLoader []CertKeyFilePair
|
type FileLoader []CertKeyFilePair
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (FileLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "tls.certificates.load_files",
|
||||||
|
New: func() caddy.Module { return new(FileLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CertKeyFilePair pairs certificate and key file names along with their
|
// CertKeyFilePair pairs certificate and key file names along with their
|
||||||
// encoding format so that they can be loaded from disk.
|
// encoding format so that they can be loaded from disk.
|
||||||
type CertKeyFilePair struct {
|
type CertKeyFilePair struct {
|
||||||
|
|
|
@ -28,10 +28,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(FolderLoader{})
|
||||||
Name: "tls.certificates.load_folders",
|
|
||||||
New: func() interface{} { return FolderLoader{} },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FolderLoader loads certificates and their associated keys from disk
|
// FolderLoader loads certificates and their associated keys from disk
|
||||||
|
@ -39,6 +36,14 @@ func init() {
|
||||||
// files which contain both a certificate and a key.
|
// files which contain both a certificate and a key.
|
||||||
type FolderLoader []string
|
type FolderLoader []string
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (FolderLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "tls.certificates.load_folders",
|
||||||
|
New: func() caddy.Module { return new(FolderLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadCertificates loads all the certificates+keys in the directories
|
// LoadCertificates loads all the certificates+keys in the directories
|
||||||
// listed in fl from all files ending with .pem. This method of loading
|
// listed in fl from all files ending with .pem. This method of loading
|
||||||
// certificates expects the certificate and key to be bundled into the
|
// certificates expects the certificate and key to be bundled into the
|
||||||
|
|
|
@ -20,14 +20,19 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(MatchServerName{})
|
||||||
|
}
|
||||||
|
|
||||||
// MatchServerName matches based on SNI.
|
// MatchServerName matches based on SNI.
|
||||||
type MatchServerName []string
|
type MatchServerName []string
|
||||||
|
|
||||||
func init() {
|
// CaddyModule returns the Caddy module information.
|
||||||
caddy.RegisterModule(caddy.Module{
|
func (MatchServerName) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
Name: "tls.handshake_match.sni",
|
Name: "tls.handshake_match.sni",
|
||||||
New: func() interface{} { return MatchServerName{} },
|
New: func() caddy.Module { return new(MatchServerName) },
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match matches hello based on SNI.
|
// Match matches hello based on SNI.
|
||||||
|
|
|
@ -24,10 +24,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(standardSTEKProvider{})
|
||||||
Name: "tls.stek.standard",
|
|
||||||
New: func() interface{} { return new(standardSTEKProvider) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type standardSTEKProvider struct {
|
type standardSTEKProvider struct {
|
||||||
|
@ -35,6 +32,14 @@ type standardSTEKProvider struct {
|
||||||
timer *time.Timer
|
timer *time.Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (standardSTEKProvider) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "tls.stek.standard",
|
||||||
|
New: func() caddy.Module { return new(standardSTEKProvider) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize sets the configuration for s and returns the starting keys.
|
// Initialize sets the configuration for s and returns the starting keys.
|
||||||
func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) {
|
func (s *standardSTEKProvider) Initialize(config *caddytls.SessionTicketService) ([][32]byte, error) {
|
||||||
// keep a reference to the config; we'll need it when rotating keys
|
// keep a reference to the config; we'll need it when rotating keys
|
||||||
|
|
|
@ -30,10 +30,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(caddy.Module{
|
caddy.RegisterModule(TLS{})
|
||||||
Name: "tls",
|
|
||||||
New: func() interface{} { return new(TLS) },
|
|
||||||
})
|
|
||||||
|
|
||||||
// opt-in TLS 1.3 for Go1.12
|
// opt-in TLS 1.3 for Go1.12
|
||||||
// TODO: remove this line when Go1.13 is released.
|
// TODO: remove this line when Go1.13 is released.
|
||||||
|
@ -53,6 +50,14 @@ type TLS struct {
|
||||||
ctx caddy.Context
|
ctx caddy.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (TLS) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
Name: "tls",
|
||||||
|
New: func() caddy.Module { return new(TLS) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Provision sets up the configuration for the TLS app.
|
// Provision sets up the configuration for the TLS app.
|
||||||
func (t *TLS) Provision(ctx caddy.Context) error {
|
func (t *TLS) Provision(ctx caddy.Context) error {
|
||||||
t.ctx = ctx
|
t.ctx = ctx
|
||||||
|
@ -73,7 +78,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading TLS automation management module: %s", err)
|
return fmt.Errorf("loading TLS automation management module: %s", err)
|
||||||
}
|
}
|
||||||
t.Automation.Policies[i].Management = val.(managerMaker)
|
t.Automation.Policies[i].Management = val.(ManagerMaker)
|
||||||
t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help?
|
t.Automation.Policies[i].ManagementRaw = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +242,7 @@ type AutomationPolicy struct {
|
||||||
Hosts []string `json:"hosts,omitempty"`
|
Hosts []string `json:"hosts,omitempty"`
|
||||||
ManagementRaw json.RawMessage `json:"management,omitempty"`
|
ManagementRaw json.RawMessage `json:"management,omitempty"`
|
||||||
|
|
||||||
Management managerMaker `json:"-"`
|
Management ManagerMaker `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
|
// makeCertMagicConfig converts ap into a CertMagic config. Passing onDemand
|
||||||
|
@ -252,7 +257,7 @@ func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy.Context) certmagic.Conf
|
||||||
}
|
}
|
||||||
|
|
||||||
return certmagic.Config{
|
return certmagic.Config{
|
||||||
NewManager: ap.Management.newManager,
|
NewManager: ap.Management.NewManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,9 +295,9 @@ type RateLimit struct {
|
||||||
Burst int `json:"burst,omitempty"`
|
Burst int `json:"burst,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// managerMaker makes a certificate manager.
|
// ManagerMaker makes a certificate manager.
|
||||||
type managerMaker interface {
|
type ManagerMaker interface {
|
||||||
newManager(interactive bool) (certmagic.Manager, error)
|
NewManager(interactive bool) (certmagic.Manager, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// These perpetual values are used for on-demand TLS.
|
// These perpetual values are used for on-demand TLS.
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
func TestGetModules(t *testing.T) {
|
func TestGetModules(t *testing.T) {
|
||||||
modulesMu.Lock()
|
modulesMu.Lock()
|
||||||
modules = map[string]Module{
|
modules = map[string]ModuleInfo{
|
||||||
"a": {Name: "a"},
|
"a": {Name: "a"},
|
||||||
"a.b": {Name: "a.b"},
|
"a.b": {Name: "a.b"},
|
||||||
"a.b.c": {Name: "a.b.c"},
|
"a.b.c": {Name: "a.b.c"},
|
||||||
|
@ -38,11 +38,11 @@ func TestGetModules(t *testing.T) {
|
||||||
|
|
||||||
for i, tc := range []struct {
|
for i, tc := range []struct {
|
||||||
input string
|
input string
|
||||||
expect []Module
|
expect []ModuleInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "",
|
input: "",
|
||||||
expect: []Module{
|
expect: []ModuleInfo{
|
||||||
{Name: "a"},
|
{Name: "a"},
|
||||||
{Name: "b"},
|
{Name: "b"},
|
||||||
{Name: "c"},
|
{Name: "c"},
|
||||||
|
@ -50,7 +50,7 @@ func TestGetModules(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a",
|
input: "a",
|
||||||
expect: []Module{
|
expect: []ModuleInfo{
|
||||||
{Name: "a.b"},
|
{Name: "a.b"},
|
||||||
{Name: "a.c"},
|
{Name: "a.c"},
|
||||||
{Name: "a.d"},
|
{Name: "a.d"},
|
||||||
|
@ -58,7 +58,7 @@ func TestGetModules(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "a.b",
|
input: "a.b",
|
||||||
expect: []Module{
|
expect: []ModuleInfo{
|
||||||
{Name: "a.b.c"},
|
{Name: "a.b.c"},
|
||||||
{Name: "a.b.cd"},
|
{Name: "a.b.cd"},
|
||||||
},
|
},
|
||||||
|
@ -68,7 +68,7 @@ func TestGetModules(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "b",
|
input: "b",
|
||||||
expect: []Module{
|
expect: []ModuleInfo{
|
||||||
{Name: "b.a"},
|
{Name: "b.a"},
|
||||||
{Name: "b.b"},
|
{Name: "b.b"},
|
||||||
},
|
},
|
||||||
|
|
13
storage.go
13
storage.go
|
@ -23,10 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterModule(Module{
|
RegisterModule(fileStorage{})
|
||||||
Name: "caddy.storage.file_system",
|
|
||||||
New: func() interface{} { return new(fileStorage) },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageConverter is a type that can convert itself
|
// StorageConverter is a type that can convert itself
|
||||||
|
@ -43,6 +40,14 @@ type fileStorage struct {
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (fileStorage) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
Name: "caddy.storage.file_system",
|
||||||
|
New: func() Module { return new(fileStorage) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) {
|
func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) {
|
||||||
return &certmagic.FileStorage{Path: s.Root}, nil
|
return &certmagic.FileStorage{Path: s.Root}, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue