mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-02 22:27:10 +01:00
Major refactoring of middleware and parser in progress
This commit is contained in:
parent
995edf0566
commit
6029973bdc
23 changed files with 1588 additions and 245 deletions
|
@ -29,8 +29,8 @@ var directiveOrder = []directive{
|
||||||
{"redir", setup.Redir},
|
{"redir", setup.Redir},
|
||||||
{"ext", setup.Ext},
|
{"ext", setup.Ext},
|
||||||
{"basicauth", setup.BasicAuth},
|
{"basicauth", setup.BasicAuth},
|
||||||
//{"proxy", setup.Proxy},
|
{"proxy", setup.Proxy},
|
||||||
// {"fastcgi", setup.FastCGI},
|
{"fastcgi", setup.FastCGI},
|
||||||
// {"websocket", setup.WebSocket},
|
// {"websocket", setup.WebSocket},
|
||||||
// {"markdown", setup.Markdown},
|
// {"markdown", setup.Markdown},
|
||||||
// {"templates", setup.Templates},
|
// {"templates", setup.Templates},
|
||||||
|
|
213
config/parse/dispenser.go
Normal file
213
config/parse/dispenser.go
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dispenser is a type that dispenses tokens, similarly to a lexer,
|
||||||
|
// except that it can do so with some notion of structure and has
|
||||||
|
// some really convenient methods.
|
||||||
|
type Dispenser struct {
|
||||||
|
filename string
|
||||||
|
tokens []token
|
||||||
|
cursor int
|
||||||
|
nesting int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDispenser returns a Dispenser, ready to use for parsing the given input.
|
||||||
|
func NewDispenser(filename string, input io.Reader) Dispenser {
|
||||||
|
return Dispenser{
|
||||||
|
filename: filename,
|
||||||
|
tokens: allTokens(input),
|
||||||
|
cursor: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDispenserTokens returns a Dispenser filled with the given tokens.
|
||||||
|
func NewDispenserTokens(filename string, tokens []token) Dispenser {
|
||||||
|
return Dispenser{
|
||||||
|
filename: filename,
|
||||||
|
tokens: tokens,
|
||||||
|
cursor: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next loads the next token. Returns true if a token
|
||||||
|
// was loaded; false otherwise. If false, all tokens
|
||||||
|
// have already been consumed.
|
||||||
|
func (d *Dispenser) Next() bool {
|
||||||
|
if d.cursor < len(d.tokens)-1 {
|
||||||
|
d.cursor++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextArg loads the next token if it is on the same
|
||||||
|
// line. Returns true if a token was loaded; false
|
||||||
|
// otherwise. If false, all tokens on the line have
|
||||||
|
// been consumed.
|
||||||
|
func (d *Dispenser) NextArg() bool {
|
||||||
|
if d.cursor < 0 {
|
||||||
|
d.cursor++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if d.cursor >= len(d.tokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d.cursor < len(d.tokens)-1 &&
|
||||||
|
(d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].line) {
|
||||||
|
d.cursor++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextLine loads the next token only if it is not on the same
|
||||||
|
// line as the current token, and returns true if a token was
|
||||||
|
// loaded; false otherwise. If false, there is not another token
|
||||||
|
// or it is on the same line.
|
||||||
|
func (d *Dispenser) NextLine() bool {
|
||||||
|
if d.cursor < 0 {
|
||||||
|
d.cursor++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if d.cursor >= len(d.tokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d.cursor < len(d.tokens)-1 &&
|
||||||
|
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].line {
|
||||||
|
d.cursor++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextBlock can be used as the condition of a for loop
|
||||||
|
// to load the next token as long as it opens a block or
|
||||||
|
// is already in a block. It returns true if a token was
|
||||||
|
// loaded, or false when the block's closing curly brace
|
||||||
|
// was loaded and thus the block ended. Nested blocks are
|
||||||
|
// not supported.
|
||||||
|
func (d *Dispenser) NextBlock() bool {
|
||||||
|
if d.nesting > 0 {
|
||||||
|
d.Next()
|
||||||
|
if d.Val() == "}" {
|
||||||
|
d.nesting--
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !d.NextArg() { // block must open on same line
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d.Val() != "{" {
|
||||||
|
d.cursor-- // roll back if not opening brace
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d.Next()
|
||||||
|
d.nesting++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val gets the text of the current token. If there is no token
|
||||||
|
// loaded, it returns empty string.
|
||||||
|
func (d *Dispenser) Val() string {
|
||||||
|
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return d.tokens[d.cursor].text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line gets the line number of the current token. If there is no token
|
||||||
|
// loaded, it returns 0.
|
||||||
|
func (d *Dispenser) Line() int {
|
||||||
|
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return d.tokens[d.cursor].line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args is a convenience function that loads the next arguments
|
||||||
|
// (tokens on the same line) into an arbitrary number of strings
|
||||||
|
// pointed to in targets. If there are fewer tokens available
|
||||||
|
// than string pointers, the remaining strings will not be changed
|
||||||
|
// and false will be returned. If there were enough tokens available
|
||||||
|
// to fill the arguments, then true will be returned.
|
||||||
|
func (d *Dispenser) Args(targets ...*string) bool {
|
||||||
|
enough := true
|
||||||
|
for i := 0; i < len(targets); i++ {
|
||||||
|
if !d.NextArg() {
|
||||||
|
enough = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
*targets[i] = d.Val()
|
||||||
|
}
|
||||||
|
return enough
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||||
|
// into a slice and returns them. Open curly brace tokens also indicate
|
||||||
|
// the end of arguments, and the curly brace is not included in
|
||||||
|
// the return value nor is it loaded.
|
||||||
|
func (d *Dispenser) RemainingArgs() []string {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
for d.NextArg() {
|
||||||
|
if d.Val() == "{" {
|
||||||
|
d.cursor--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, d.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgErr returns an argument error, meaning that another
|
||||||
|
// argument was expected but not found. In other words,
|
||||||
|
// a line break or open curly brace was encountered instead of
|
||||||
|
// an argument.
|
||||||
|
func (d *Dispenser) ArgErr() error {
|
||||||
|
if d.Val() == "{" {
|
||||||
|
return d.Err("Unexpected token '{', expecting argument")
|
||||||
|
}
|
||||||
|
return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyntaxErr creates a generic syntax error which explains what was
|
||||||
|
// found and what was expected.
|
||||||
|
func (d *Dispenser) SyntaxErr(expected string) error {
|
||||||
|
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.filename, d.Line(), d.Val(), expected)
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EofErr returns an EOF error, meaning that end of input
|
||||||
|
// was found when another token was expected.
|
||||||
|
func (d *Dispenser) EofErr() error {
|
||||||
|
return d.Errf("Unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err generates a custom parse error with a message of msg.
|
||||||
|
func (d *Dispenser) Err(msg string) error {
|
||||||
|
msg = fmt.Sprintf("%s:%d - Parse error: %s", d.filename, d.Line(), msg)
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errf is like Err, but for formatted error messages
|
||||||
|
func (d *Dispenser) Errf(format string, args ...interface{}) error {
|
||||||
|
return d.Err(fmt.Sprintf(format, args...)) // TODO: I think args needs to be args...
|
||||||
|
}
|
||||||
|
|
||||||
|
// numLineBreaks counts how many line breaks are in the token
|
||||||
|
// value given by the token index tknIdx. It returns 0 if the
|
||||||
|
// token does not exist or there are no line breaks.
|
||||||
|
func (d *Dispenser) numLineBreaks(tknIdx int) int {
|
||||||
|
if tknIdx < 0 || tknIdx >= len(d.tokens) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return strings.Count(d.tokens[tknIdx].text, "\n")
|
||||||
|
}
|
114
config/parse/lexer.go
Normal file
114
config/parse/lexer.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// lexer is a utility which can get values, token by
|
||||||
|
// token, from a Reader. A token is a word, and tokens
|
||||||
|
// are separated by whitespace. A word can be enclosed
|
||||||
|
// in quotes if it contains whitespace.
|
||||||
|
lexer struct {
|
||||||
|
reader *bufio.Reader
|
||||||
|
token token
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
// token represents a single parsable unit.
|
||||||
|
token struct {
|
||||||
|
line int
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// load prepares the lexer to scan an input for tokens.
|
||||||
|
func (l *lexer) load(input io.Reader) error {
|
||||||
|
l.reader = bufio.NewReader(input)
|
||||||
|
l.line = 1
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// next loads the next token into the lexer.
|
||||||
|
// A token is delimited by whitespace, unless
|
||||||
|
// the token starts with a quotes character (")
|
||||||
|
// in which case the token goes until the closing
|
||||||
|
// quotes (the enclosing quotes are not included).
|
||||||
|
// The rest of the line is skipped if a "#"
|
||||||
|
// character is read in. Returns true if a token
|
||||||
|
// was loaded; false otherwise.
|
||||||
|
func (l *lexer) next() bool {
|
||||||
|
var val []rune
|
||||||
|
var comment, quoted, escaped bool
|
||||||
|
|
||||||
|
makeToken := func() bool {
|
||||||
|
l.token.text = string(val)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ch, _, err := l.reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if len(val) > 0 {
|
||||||
|
return makeToken()
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if quoted {
|
||||||
|
if !escaped {
|
||||||
|
if ch == '\\' {
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
} else if ch == '"' {
|
||||||
|
quoted = false
|
||||||
|
return makeToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ch == '\n' {
|
||||||
|
l.line++
|
||||||
|
}
|
||||||
|
val = append(val, ch)
|
||||||
|
escaped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if unicode.IsSpace(ch) {
|
||||||
|
if ch == '\r' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch == '\n' {
|
||||||
|
l.line++
|
||||||
|
comment = false
|
||||||
|
}
|
||||||
|
if len(val) > 0 {
|
||||||
|
return makeToken()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '#' {
|
||||||
|
comment = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if comment {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(val) == 0 {
|
||||||
|
l.token = token{line: l.line}
|
||||||
|
if ch == '"' {
|
||||||
|
quoted = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val = append(val, ch)
|
||||||
|
}
|
||||||
|
}
|
27
config/parse/parse.go
Normal file
27
config/parse/parse.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Package parse provides facilities for parsing configuration files.
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// ServerBlocks parses the input just enough to organize tokens,
|
||||||
|
// in order, by server block. No further parsing is performed.
|
||||||
|
// Server blocks are returned in the order in which they appear.
|
||||||
|
func ServerBlocks(filename string, input io.Reader) ([]serverBlock, error) {
|
||||||
|
p := parser{Dispenser: NewDispenser(filename, input)}
|
||||||
|
blocks, err := p.parseAll()
|
||||||
|
return blocks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// allTokens lexes the entire input, but does not parse it.
|
||||||
|
// It returns all the tokens from the input, unstructured
|
||||||
|
// and in order.
|
||||||
|
func allTokens(input io.Reader) (tokens []token) {
|
||||||
|
l := new(lexer)
|
||||||
|
l.load(input)
|
||||||
|
for l.next() {
|
||||||
|
tokens = append(tokens, l.token)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ValidDirectives = make(map[string]struct{})
|
300
config/parse/parsing.go
Normal file
300
config/parse/parsing.go
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
Dispenser
|
||||||
|
block multiServerBlock // current server block being parsed
|
||||||
|
eof bool // if we encounter a valid EOF in a hard place
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseAll() ([]serverBlock, error) {
|
||||||
|
var blocks []serverBlock
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
err := p.parseOne()
|
||||||
|
if err != nil {
|
||||||
|
return blocks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// explode the multiServerBlock into multiple serverBlocks
|
||||||
|
for _, addr := range p.block.addresses {
|
||||||
|
blocks = append(blocks, serverBlock{
|
||||||
|
Host: addr.host,
|
||||||
|
Port: addr.port,
|
||||||
|
Tokens: p.block.tokens,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseOne() error {
|
||||||
|
p.block = multiServerBlock{tokens: make(map[string][]token)}
|
||||||
|
|
||||||
|
err := p.begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) begin() error {
|
||||||
|
err := p.addresses()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.blockContents()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) addresses() error {
|
||||||
|
var expectingAnother bool
|
||||||
|
|
||||||
|
for {
|
||||||
|
tkn, startLine := p.Val(), p.Line()
|
||||||
|
|
||||||
|
// Open brace definitely indicates end of addresses
|
||||||
|
if tkn == "{" {
|
||||||
|
if expectingAnother {
|
||||||
|
return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing comma indicates another address will follow, which
|
||||||
|
// may possibly be on the next line
|
||||||
|
if tkn[len(tkn)-1] == ',' {
|
||||||
|
tkn = tkn[:len(tkn)-1]
|
||||||
|
expectingAnother = true
|
||||||
|
} else {
|
||||||
|
expectingAnother = false // but we may still see another one on this line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and save this address
|
||||||
|
host, port, err := standardAddress(tkn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.block.addresses = append(p.block.addresses, address{host, port})
|
||||||
|
|
||||||
|
// Advance token and possibly break out of loop or return error
|
||||||
|
hasNext := p.Next()
|
||||||
|
if expectingAnother && !hasNext {
|
||||||
|
return p.EofErr()
|
||||||
|
}
|
||||||
|
if !expectingAnother && p.Line() > startLine {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !hasNext {
|
||||||
|
p.eof = true
|
||||||
|
break // EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) blockContents() error {
|
||||||
|
errOpenCurlyBrace := p.openCurlyBrace()
|
||||||
|
if errOpenCurlyBrace != nil {
|
||||||
|
// single-server configs don't need curly braces
|
||||||
|
p.cursor--
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.eof {
|
||||||
|
// this happens if the Caddyfile consists of only
|
||||||
|
// a line of addresses and nothing else
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.directives()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only look for close curly brace if there was an opening
|
||||||
|
if errOpenCurlyBrace == nil {
|
||||||
|
err = p.closeCurlyBrace()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// directives parses through all the lines for directives
|
||||||
|
// and it expects the next token to be the first
|
||||||
|
// directive. It goes until EOF or closing curly brace
|
||||||
|
// which ends the server block.
|
||||||
|
func (p *parser) directives() error {
|
||||||
|
for p.Next() {
|
||||||
|
// end of server block
|
||||||
|
if p.Val() == "}" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case: import directive replaces tokens during parse-time
|
||||||
|
if p.Val() == "import" {
|
||||||
|
err := p.doImport()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal case: parse a directive on this line
|
||||||
|
if err := p.directive(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doImport swaps out the import directive and its argument
|
||||||
|
// (a total of 2 tokens) with the tokens in the file specified.
|
||||||
|
// When the function returns, the cursor is on the token before
|
||||||
|
// where the import directive was. In other words, call Next()
|
||||||
|
// to access the first token that was imported.
|
||||||
|
func (p *parser) doImport() error {
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
importFile := p.Val()
|
||||||
|
if p.NextArg() {
|
||||||
|
return p.Err("Import allows only one file to import")
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(importFile)
|
||||||
|
if err != nil {
|
||||||
|
return p.Errf("Could not import %s - %v", importFile, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
importedTokens := allTokens(file)
|
||||||
|
|
||||||
|
// Splice out the import directive and its argument (2 tokens total)
|
||||||
|
// and insert the imported tokens.
|
||||||
|
tokensBefore := p.tokens[:p.cursor-1]
|
||||||
|
tokensAfter := p.tokens[p.cursor+1:]
|
||||||
|
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
|
||||||
|
p.cursor -= 2
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// directive collects tokens until the directive's scope
|
||||||
|
// closes (either end of line or end of curly brace block).
|
||||||
|
// It expects the currently-loaded token to be a directive
|
||||||
|
// (or } that ends a server block). The collected tokens
|
||||||
|
// are loaded into the current server block for later use
|
||||||
|
// by directive setup functions.
|
||||||
|
func (p *parser) directive() error {
|
||||||
|
dir := p.Val()
|
||||||
|
line := p.Line()
|
||||||
|
nesting := 0
|
||||||
|
|
||||||
|
if _, ok := ValidDirectives[dir]; !ok {
|
||||||
|
return p.Errf("Unknown directive '%s'", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The directive itself is appended as a relevant token
|
||||||
|
p.block.tokens[dir] = append(p.block.tokens[dir], p.tokens[p.cursor])
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
if p.Val() == "{" {
|
||||||
|
nesting++
|
||||||
|
} else if p.Line()+p.numLineBreaks(p.cursor) > line && nesting == 0 {
|
||||||
|
p.cursor-- // read too far
|
||||||
|
break
|
||||||
|
} else if p.Val() == "}" && nesting > 0 {
|
||||||
|
nesting--
|
||||||
|
} else if p.Val() == "}" && nesting == 0 {
|
||||||
|
return p.Err("Unexpected '}' because no matching opening brace")
|
||||||
|
}
|
||||||
|
p.block.tokens[dir] = append(p.block.tokens[dir], p.tokens[p.cursor])
|
||||||
|
}
|
||||||
|
|
||||||
|
if nesting > 0 {
|
||||||
|
return p.EofErr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openCurlyBrace expects the current token to be an
|
||||||
|
// opening curly brace. This acts like an assertion
|
||||||
|
// because it returns an error if the token is not
|
||||||
|
// a opening curly brace. It does not advance the token.
|
||||||
|
func (p *parser) openCurlyBrace() error {
|
||||||
|
if p.Val() != "{" {
|
||||||
|
return p.SyntaxErr("{")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeCurlyBrace expects the current token to be
|
||||||
|
// a closing curly brace. This acts like an assertion
|
||||||
|
// because it returns an error if the token is not
|
||||||
|
// a closing curly brace. It does not advance the token.
|
||||||
|
func (p *parser) closeCurlyBrace() error {
|
||||||
|
if p.Val() != "}" {
|
||||||
|
return p.SyntaxErr("}")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// standardAddress turns the accepted host and port patterns
|
||||||
|
// into a format accepted by net.Dial.
|
||||||
|
func standardAddress(str string) (host, port string, err error) {
|
||||||
|
var schemePort string
|
||||||
|
|
||||||
|
if strings.HasPrefix(str, "https://") {
|
||||||
|
schemePort = "https"
|
||||||
|
str = str[8:]
|
||||||
|
} else if strings.HasPrefix(str, "http://") {
|
||||||
|
schemePort = "http"
|
||||||
|
str = str[7:]
|
||||||
|
} else if !strings.Contains(str, ":") {
|
||||||
|
str += ":" // + Port
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err = net.SplitHostPort(str)
|
||||||
|
if err != nil && schemePort != "" {
|
||||||
|
host = str
|
||||||
|
port = schemePort // assume port from scheme
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// serverBlock stores tokens by directive name for a
|
||||||
|
// single host:port (address)
|
||||||
|
serverBlock struct {
|
||||||
|
Host, Port string
|
||||||
|
Tokens map[string][]token // directive name to tokens (including directive)
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiServerBlock is the same as serverBlock but for
|
||||||
|
// multiple addresses that share the same tokens
|
||||||
|
multiServerBlock struct {
|
||||||
|
addresses []address
|
||||||
|
tokens map[string][]token
|
||||||
|
}
|
||||||
|
|
||||||
|
address struct {
|
||||||
|
host, port string
|
||||||
|
}
|
||||||
|
)
|
53
config/setup/basicauth.go
Normal file
53
config/setup/basicauth.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/basicauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicAuth configures a new BasicAuth middleware instance.
|
||||||
|
func BasicAuth(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rules, err := basicAuthParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
basic := basicauth.BasicAuth{Rules: rules}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
basic.Next = next
|
||||||
|
return basic
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
|
||||||
|
var rules []basicauth.Rule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var rule basicauth.Rule
|
||||||
|
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 2:
|
||||||
|
rule.Username = args[0]
|
||||||
|
rule.Password = args[1]
|
||||||
|
for c.NextBlock() {
|
||||||
|
rule.Resources = append(rule.Resources, c.Val())
|
||||||
|
if c.NextArg() {
|
||||||
|
return rules, c.Errf("Expecting only one resource per line (extra '%s')", c.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
rule.Resources = append(rule.Resources, args[0])
|
||||||
|
rule.Username = args[1]
|
||||||
|
rule.Password = args[2]
|
||||||
|
default:
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
11
config/setup/controller.go
Normal file
11
config/setup/controller.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/config/parse"
|
||||||
|
"github.com/mholt/caddy/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
*server.Config
|
||||||
|
parse.Dispenser
|
||||||
|
}
|
103
config/setup/errors.go
Normal file
103
config/setup/errors.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors configures a new gzip middleware instance.
|
||||||
|
func Errors(c *Controller) (middleware.Middleware, error) {
|
||||||
|
handler, err := errorsParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the log file for writing when the server starts
|
||||||
|
c.Startup = append(c.Startup, func() error {
|
||||||
|
var err error
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
if handler.LogFile == "stdout" {
|
||||||
|
file = os.Stdout
|
||||||
|
} else if handler.LogFile == "stderr" {
|
||||||
|
file = os.Stderr
|
||||||
|
} else if handler.LogFile != "" {
|
||||||
|
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Log = log.New(file, "", 0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
handler.Next = next
|
||||||
|
return handler
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
|
||||||
|
// Very important that we make a pointer because the Startup
|
||||||
|
// function that opens the log file must have access to the
|
||||||
|
// same instance of the handler, not a copy.
|
||||||
|
handler := &errors.ErrorHandler{ErrorPages: make(map[int]string)}
|
||||||
|
|
||||||
|
optionalBlock := func() (bool, error) {
|
||||||
|
var hadBlock bool
|
||||||
|
|
||||||
|
for c.NextBlock() {
|
||||||
|
hadBlock = true
|
||||||
|
|
||||||
|
what := c.Val()
|
||||||
|
if !c.NextArg() {
|
||||||
|
return hadBlock, c.ArgErr()
|
||||||
|
}
|
||||||
|
where := c.Val()
|
||||||
|
|
||||||
|
if what == "log" {
|
||||||
|
handler.LogFile = where
|
||||||
|
} else {
|
||||||
|
// Error page; ensure it exists
|
||||||
|
where = path.Join(c.Root, where)
|
||||||
|
f, err := os.Open(where)
|
||||||
|
if err != nil {
|
||||||
|
return hadBlock, c.Err("Unable to open error page '" + where + "': " + err.Error())
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
whatInt, err := strconv.Atoi(what)
|
||||||
|
if err != nil {
|
||||||
|
return hadBlock, c.Err("Expecting a numeric status code, got '" + what + "'")
|
||||||
|
}
|
||||||
|
handler.ErrorPages[whatInt] = where
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hadBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
// Configuration may be in a block
|
||||||
|
hadBlock, err := optionalBlock()
|
||||||
|
if err != nil {
|
||||||
|
return handler, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, the only argument would be an error log file name
|
||||||
|
if !hadBlock {
|
||||||
|
if c.NextArg() {
|
||||||
|
handler.LogFile = c.Val()
|
||||||
|
} else {
|
||||||
|
handler.LogFile = errors.DefaultLogFilename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler, nil
|
||||||
|
}
|
54
config/setup/ext.go
Normal file
54
config/setup/ext.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/extensions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ext configures a new instance of 'extensions' middleware for clean URLs.
|
||||||
|
func Ext(c *Controller) (middleware.Middleware, error) {
|
||||||
|
root := c.Root
|
||||||
|
|
||||||
|
exts, err := extParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return extensions.Ext{
|
||||||
|
Next: next,
|
||||||
|
Extensions: exts,
|
||||||
|
Root: root,
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extParse sets up an instance of extension middleware
|
||||||
|
// from a middleware controller and returns a list of extensions.
|
||||||
|
func extParse(c *Controller) ([]string, error) {
|
||||||
|
var exts []string
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
// At least one extension is required
|
||||||
|
if !c.NextArg() {
|
||||||
|
return exts, c.ArgErr()
|
||||||
|
}
|
||||||
|
exts = append(exts, c.Val())
|
||||||
|
|
||||||
|
// Tack on any other extensions that may have been listed
|
||||||
|
exts = append(exts, c.RemainingArgs()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceExists returns true if the file specified at
|
||||||
|
// root + path exists; false otherwise.
|
||||||
|
func resourceExists(root, path string) bool {
|
||||||
|
_, err := os.Stat(root + path)
|
||||||
|
// technically we should use os.IsNotExist(err)
|
||||||
|
// but we don't handle any other kinds of errors anyway
|
||||||
|
return err == nil
|
||||||
|
}
|
105
config/setup/fastcgi.go
Normal file
105
config/setup/fastcgi.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/fastcgi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FastCGI configures a new FastCGI middleware instance.
|
||||||
|
func FastCGI(c *Controller) (middleware.Middleware, error) {
|
||||||
|
root, err := filepath.Abs(c.Root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := fastcgiParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return fastcgi.Handler{
|
||||||
|
Next: next,
|
||||||
|
Rules: rules,
|
||||||
|
Root: root,
|
||||||
|
SoftwareName: "Caddy", // TODO: Once generators are not in the same pkg as handler, obtain this from some global const
|
||||||
|
SoftwareVersion: "", // TODO: Get this from some global const too
|
||||||
|
// TODO: Set ServerName and ServerPort to correct values... (as user defined in config)
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastcgiParse(c *Controller) ([]fastcgi.Rule, error) {
|
||||||
|
var rules []fastcgi.Rule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var rule fastcgi.Rule
|
||||||
|
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
case 1:
|
||||||
|
rule.Path = "/"
|
||||||
|
rule.Address = args[0]
|
||||||
|
case 2:
|
||||||
|
rule.Path = args[0]
|
||||||
|
rule.Address = args[1]
|
||||||
|
case 3:
|
||||||
|
rule.Path = args[0]
|
||||||
|
rule.Address = args[1]
|
||||||
|
err := fastcgiPreset(args[2], &rule)
|
||||||
|
if err != nil {
|
||||||
|
return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
case "ext":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.Ext = c.Val()
|
||||||
|
case "split":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.SplitPath = c.Val()
|
||||||
|
case "index":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.IndexFile = c.Val()
|
||||||
|
case "env":
|
||||||
|
envArgs := c.RemainingArgs()
|
||||||
|
if len(envArgs) < 2 {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastcgiPreset configures rule according to name. It returns an error if
|
||||||
|
// name is not a recognized preset name.
|
||||||
|
func fastcgiPreset(name string, rule *fastcgi.Rule) error {
|
||||||
|
switch name {
|
||||||
|
case "php":
|
||||||
|
rule.Ext = ".php"
|
||||||
|
rule.SplitPath = ".php"
|
||||||
|
rule.IndexFile = "index.php"
|
||||||
|
default:
|
||||||
|
return errors.New(name + " is not a valid preset name")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
13
config/setup/gzip.go
Normal file
13
config/setup/gzip.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/gzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gzip configures a new gzip middleware instance.
|
||||||
|
func Gzip(c *Controller) (middleware.Middleware, error) {
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return gzip.Gzip{Next: next}
|
||||||
|
}, nil
|
||||||
|
}
|
84
config/setup/headers.go
Normal file
84
config/setup/headers.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/headers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Headers configures a new Headers middleware instance.
|
||||||
|
func Headers(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rules, err := headersParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return headers.Headers{Next: next, Rules: rules}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func headersParse(c *Controller) ([]headers.Rule, error) {
|
||||||
|
var rules []headers.Rule
|
||||||
|
|
||||||
|
for c.NextLine() {
|
||||||
|
var head headers.Rule
|
||||||
|
var isNewPattern bool
|
||||||
|
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rules, c.ArgErr()
|
||||||
|
}
|
||||||
|
pattern := c.Val()
|
||||||
|
|
||||||
|
// See if we already have a definition for this URL pattern...
|
||||||
|
for _, h := range rules {
|
||||||
|
if h.Url == pattern {
|
||||||
|
head = h
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...otherwise, this is a new pattern
|
||||||
|
if head.Url == "" {
|
||||||
|
head.Url = pattern
|
||||||
|
isNewPattern = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.NextBlock() {
|
||||||
|
// A block of headers was opened...
|
||||||
|
|
||||||
|
h := headers.Header{Name: c.Val()}
|
||||||
|
|
||||||
|
if c.NextArg() {
|
||||||
|
h.Value = c.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
head.Headers = append(head.Headers, h)
|
||||||
|
}
|
||||||
|
if c.NextArg() {
|
||||||
|
// ... or single header was defined as an argument instead.
|
||||||
|
|
||||||
|
h := headers.Header{Name: c.Val()}
|
||||||
|
|
||||||
|
h.Value = c.Val()
|
||||||
|
|
||||||
|
if c.NextArg() {
|
||||||
|
h.Value = c.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
head.Headers = append(head.Headers, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNewPattern {
|
||||||
|
rules = append(rules, head)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(rules); i++ {
|
||||||
|
if rules[i].Url == pattern {
|
||||||
|
rules[i] = head
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
90
config/setup/log.go
Normal file
90
config/setup/log.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
caddylog "github.com/mholt/caddy/middleware/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Log(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rules, err := logParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the log files for writing when the server starts
|
||||||
|
c.Startup = append(c.Startup, func() error {
|
||||||
|
for i := 0; i < len(rules); i++ {
|
||||||
|
var err error
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
if rules[i].OutputFile == "stdout" {
|
||||||
|
file = os.Stdout
|
||||||
|
} else if rules[i].OutputFile == "stderr" {
|
||||||
|
file = os.Stderr
|
||||||
|
} else {
|
||||||
|
file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rules[i].Log = log.New(file, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return caddylog.Logger{Next: next, Rules: rules}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logParse(c *Controller) ([]caddylog.LogRule, error) {
|
||||||
|
var rules []caddylog.LogRule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
// Nothing specified; use defaults
|
||||||
|
rules = append(rules, caddylog.LogRule{
|
||||||
|
PathScope: "/",
|
||||||
|
OutputFile: caddylog.DefaultLogFilename,
|
||||||
|
Format: caddylog.DefaultLogFormat,
|
||||||
|
})
|
||||||
|
} else if len(args) == 1 {
|
||||||
|
// Only an output file specified
|
||||||
|
rules = append(rules, caddylog.LogRule{
|
||||||
|
PathScope: "/",
|
||||||
|
OutputFile: args[0],
|
||||||
|
Format: caddylog.DefaultLogFormat,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Path scope, output file, and maybe a format specified
|
||||||
|
|
||||||
|
format := caddylog.DefaultLogFormat
|
||||||
|
|
||||||
|
if len(args) > 2 {
|
||||||
|
switch args[2] {
|
||||||
|
case "{common}":
|
||||||
|
format = caddylog.CommonLogFormat
|
||||||
|
case "{combined}":
|
||||||
|
format = caddylog.CombinedLogFormat
|
||||||
|
default:
|
||||||
|
format = args[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rules = append(rules, caddylog.LogRule{
|
||||||
|
PathScope: args[0],
|
||||||
|
OutputFile: args[1],
|
||||||
|
Format: format,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nil
|
||||||
|
}
|
145
config/setup/proxy.go
Normal file
145
config/setup/proxy.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Proxy configures a new Proxy middleware instance.
|
||||||
|
func Proxy(c *Controller) (middleware.Middleware, error) {
|
||||||
|
if upstreams, err := newStaticUpstreams(c); err == nil {
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return proxy.Proxy{Next: next, Upstreams: upstreams}
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStaticUpstreams parses the configuration input and sets up
|
||||||
|
// static upstreams for the proxy middleware.
|
||||||
|
func newStaticUpstreams(c *Controller) ([]proxy.Upstream, error) {
|
||||||
|
var upstreams []proxy.Upstream
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
upstream := &proxy.StaticUpstream{
|
||||||
|
From: "",
|
||||||
|
Hosts: nil,
|
||||||
|
Policy: &proxy.Random{},
|
||||||
|
FailTimeout: 10 * time.Second,
|
||||||
|
MaxFails: 1,
|
||||||
|
}
|
||||||
|
var proxyHeaders http.Header
|
||||||
|
if !c.Args(&upstream.From) {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
to := c.RemainingArgs()
|
||||||
|
if len(to) == 0 {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.NextBlock() {
|
||||||
|
switch c.Val() {
|
||||||
|
case "policy":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
switch c.Val() {
|
||||||
|
case "random":
|
||||||
|
upstream.Policy = &proxy.Random{}
|
||||||
|
case "round_robin":
|
||||||
|
upstream.Policy = &proxy.RoundRobin{}
|
||||||
|
case "least_conn":
|
||||||
|
upstream.Policy = &proxy.LeastConn{}
|
||||||
|
default:
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
case "fail_timeout":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
if dur, err := time.ParseDuration(c.Val()); err == nil {
|
||||||
|
upstream.FailTimeout = dur
|
||||||
|
} else {
|
||||||
|
return upstreams, err
|
||||||
|
}
|
||||||
|
case "max_fails":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
if n, err := strconv.Atoi(c.Val()); err == nil {
|
||||||
|
upstream.MaxFails = int32(n)
|
||||||
|
} else {
|
||||||
|
return upstreams, err
|
||||||
|
}
|
||||||
|
case "health_check":
|
||||||
|
if !c.NextArg() {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
upstream.HealthCheck.Path = c.Val()
|
||||||
|
upstream.HealthCheck.Interval = 30 * time.Second
|
||||||
|
if c.NextArg() {
|
||||||
|
if dur, err := time.ParseDuration(c.Val()); err == nil {
|
||||||
|
upstream.HealthCheck.Interval = dur
|
||||||
|
} else {
|
||||||
|
return upstreams, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "proxy_header":
|
||||||
|
var header, value string
|
||||||
|
if !c.Args(&header, &value) {
|
||||||
|
return upstreams, c.ArgErr()
|
||||||
|
}
|
||||||
|
if proxyHeaders == nil {
|
||||||
|
proxyHeaders = make(map[string][]string)
|
||||||
|
}
|
||||||
|
proxyHeaders.Add(header, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream.Hosts = make([]*proxy.UpstreamHost, len(to))
|
||||||
|
for i, host := range to {
|
||||||
|
if !strings.HasPrefix(host, "http") {
|
||||||
|
host = "http://" + host
|
||||||
|
}
|
||||||
|
uh := &proxy.UpstreamHost{
|
||||||
|
Name: host,
|
||||||
|
Conns: 0,
|
||||||
|
Fails: 0,
|
||||||
|
FailTimeout: upstream.FailTimeout,
|
||||||
|
Unhealthy: false,
|
||||||
|
ExtraHeaders: proxyHeaders,
|
||||||
|
CheckDown: func(upstream *proxy.StaticUpstream) proxy.UpstreamHostDownFunc {
|
||||||
|
return func(uh *proxy.UpstreamHost) bool {
|
||||||
|
if uh.Unhealthy {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if uh.Fails >= upstream.MaxFails &&
|
||||||
|
upstream.MaxFails != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}(upstream),
|
||||||
|
}
|
||||||
|
if baseUrl, err := url.Parse(uh.Name); err == nil {
|
||||||
|
uh.ReverseProxy = proxy.NewSingleHostReverseProxy(baseUrl)
|
||||||
|
} else {
|
||||||
|
return upstreams, err
|
||||||
|
}
|
||||||
|
upstream.Hosts[i] = uh
|
||||||
|
}
|
||||||
|
|
||||||
|
if upstream.HealthCheck.Path != "" {
|
||||||
|
go upstream.HealthCheckWorker(nil)
|
||||||
|
}
|
||||||
|
upstreams = append(upstreams, upstream)
|
||||||
|
}
|
||||||
|
return upstreams, nil
|
||||||
|
}
|
77
config/setup/redir.go
Normal file
77
config/setup/redir.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/redirect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redir configures a new Redirect middleware instance.
|
||||||
|
func Redir(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rules, err := redirParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return redirect.Redirect{Next: next, Rules: rules}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirParse(c *Controller) ([]redirect.Rule, error) {
|
||||||
|
var redirects []redirect.Rule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var rule redirect.Rule
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 1:
|
||||||
|
// To specified
|
||||||
|
rule.From = "/"
|
||||||
|
rule.To = args[0]
|
||||||
|
rule.Code = http.StatusMovedPermanently
|
||||||
|
case 2:
|
||||||
|
// To and Code specified
|
||||||
|
rule.From = "/"
|
||||||
|
rule.To = args[0]
|
||||||
|
if code, ok := httpRedirs[args[1]]; !ok {
|
||||||
|
return redirects, c.Err("Invalid redirect code '" + args[1] + "'")
|
||||||
|
} else {
|
||||||
|
rule.Code = code
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
// From, To, and Code specified
|
||||||
|
rule.From = args[0]
|
||||||
|
rule.To = args[1]
|
||||||
|
if code, ok := httpRedirs[args[2]]; !ok {
|
||||||
|
return redirects, c.Err("Invalid redirect code '" + args[2] + "'")
|
||||||
|
} else {
|
||||||
|
rule.Code = code
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return redirects, c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.From == rule.To {
|
||||||
|
return redirects, c.Err("Redirect rule cannot allow From and To arguments to be the same.")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects = append(redirects, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpRedirs is a list of supported HTTP redirect codes.
|
||||||
|
var httpRedirs = map[string]int{
|
||||||
|
"300": 300,
|
||||||
|
"301": 301,
|
||||||
|
"302": 302,
|
||||||
|
"303": 303,
|
||||||
|
"304": 304,
|
||||||
|
"305": 305,
|
||||||
|
"307": 307,
|
||||||
|
"308": 308,
|
||||||
|
}
|
40
config/setup/rewrite.go
Normal file
40
config/setup/rewrite.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rewrite configures a new Rewrite middleware instance.
|
||||||
|
func Rewrite(c *Controller) (middleware.Middleware, error) {
|
||||||
|
rewrites, err := rewriteParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return rewrite.Rewrite{Next: next, Rules: rewrites}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewriteParse(c *Controller) ([]rewrite.Rule, error) {
|
||||||
|
var rewrites []rewrite.Rule
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
var rule rewrite.Rule
|
||||||
|
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rewrites, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.From = c.Val()
|
||||||
|
|
||||||
|
if !c.NextArg() {
|
||||||
|
return rewrites, c.ArgErr()
|
||||||
|
}
|
||||||
|
rule.To = c.Val()
|
||||||
|
|
||||||
|
rewrites = append(rewrites, rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewrites, nil
|
||||||
|
}
|
31
config/setup/root.go
Normal file
31
config/setup/root.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root(c *Controller) (middleware.Middleware, error) {
|
||||||
|
for c.Next() {
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
c.Root = c.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if root path exists
|
||||||
|
_, err := os.Stat(c.Root)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Allow this, because the folder might appear later.
|
||||||
|
// But make sure the user knows!
|
||||||
|
log.Printf("Warning: Root path does not exist: %s", c.Root)
|
||||||
|
} else {
|
||||||
|
return nil, c.Errf("Unable to access root path '%s': %v", c.Root, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
43
config/setup/startupshutdown.go
Normal file
43
config/setup/startupshutdown.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Startup(c *Controller) (middleware.Middleware, error) {
|
||||||
|
return nil, registerCallback(c, &c.Startup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Shutdown(c *Controller) (middleware.Middleware, error) {
|
||||||
|
return nil, registerCallback(c, &c.Shutdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerCallback registers a callback function to execute by
|
||||||
|
// using c to parse the line. It appends the callback function
|
||||||
|
// to the list of callback functions passed in by reference.
|
||||||
|
func registerCallback(c *Controller, list *[]func() error) error {
|
||||||
|
for c.Next() {
|
||||||
|
if !c.NextArg() {
|
||||||
|
return c.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
command, args, err := middleware.SplitCommandAndArgs(c.Val())
|
||||||
|
if err != nil {
|
||||||
|
return c.Err(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := func() error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
*list = append(*list, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
21
config/setup/tls.go
Normal file
21
config/setup/tls.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import "github.com/mholt/caddy/middleware"
|
||||||
|
|
||||||
|
func TLS(c *Controller) (middleware.Middleware, error) {
|
||||||
|
c.TLS.Enabled = true
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
c.TLS.Certificate = c.Val()
|
||||||
|
|
||||||
|
if !c.NextArg() {
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
}
|
||||||
|
c.TLS.Key = c.Val()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -4,7 +4,6 @@
|
||||||
package fastcgi
|
package fastcgi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -15,30 +14,6 @@ import (
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New generates a new FastCGI middleware.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
root, err := filepath.Abs(c.Root())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rules, err := parse(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
return Handler{
|
|
||||||
Next: next,
|
|
||||||
Rules: rules,
|
|
||||||
Root: root,
|
|
||||||
SoftwareName: "Caddy", // TODO: Once generators are not in the same pkg as handler, obtain this from some global const
|
|
||||||
SoftwareVersion: "", // TODO: Get this from some global const too
|
|
||||||
// TODO: Set ServerName and ServerPort to correct values... (as user defined in config)
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler is a middleware type that can handle requests as a FastCGI client.
|
// Handler is a middleware type that can handle requests as a FastCGI client.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
@ -222,78 +197,6 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse(c middleware.Controller) ([]Rule, error) {
|
|
||||||
var rules []Rule
|
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
var rule Rule
|
|
||||||
|
|
||||||
args := c.RemainingArgs()
|
|
||||||
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
|
||||||
return rules, c.ArgErr()
|
|
||||||
case 1:
|
|
||||||
rule.Path = "/"
|
|
||||||
rule.Address = args[0]
|
|
||||||
case 2:
|
|
||||||
rule.Path = args[0]
|
|
||||||
rule.Address = args[1]
|
|
||||||
case 3:
|
|
||||||
rule.Path = args[0]
|
|
||||||
rule.Address = args[1]
|
|
||||||
err := preset(args[2], &rule)
|
|
||||||
if err != nil {
|
|
||||||
return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "ext":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return rules, c.ArgErr()
|
|
||||||
}
|
|
||||||
rule.Ext = c.Val()
|
|
||||||
case "split":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return rules, c.ArgErr()
|
|
||||||
}
|
|
||||||
rule.SplitPath = c.Val()
|
|
||||||
case "index":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return rules, c.ArgErr()
|
|
||||||
}
|
|
||||||
rule.IndexFile = c.Val()
|
|
||||||
case "env":
|
|
||||||
envArgs := c.RemainingArgs()
|
|
||||||
if len(envArgs) < 2 {
|
|
||||||
return rules, c.ArgErr()
|
|
||||||
}
|
|
||||||
rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// preset configures rule according to name. It returns an error if
|
|
||||||
// name is not a recognized preset name.
|
|
||||||
func preset(name string, rule *Rule) error {
|
|
||||||
switch name {
|
|
||||||
case "php":
|
|
||||||
rule.Ext = ".php"
|
|
||||||
rule.SplitPath = ".php"
|
|
||||||
rule.IndexFile = "index.php"
|
|
||||||
default:
|
|
||||||
return errors.New(name + " is not a valid preset name")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule represents a FastCGI handling rule.
|
// Rule represents a FastCGI handling rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
// The base path to match. Required.
|
// The base path to match. Required.
|
||||||
|
|
|
@ -3,11 +3,12 @@ package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errUnreachable = errors.New("Unreachable backend")
|
var errUnreachable = errors.New("Unreachable backend")
|
||||||
|
@ -21,8 +22,8 @@ type Proxy struct {
|
||||||
// An upstream manages a pool of proxy upstream hosts. Select should return a
|
// An upstream manages a pool of proxy upstream hosts. Select should return a
|
||||||
// suitable upstream host, or nil if no such hosts are available.
|
// suitable upstream host, or nil if no such hosts are available.
|
||||||
type Upstream interface {
|
type Upstream interface {
|
||||||
// The path this upstream host should be routed on
|
//The path this upstream host should be routed on
|
||||||
From() string
|
from() string
|
||||||
// Selects an upstream host to be routed to.
|
// Selects an upstream host to be routed to.
|
||||||
Select() *UpstreamHost
|
Select() *UpstreamHost
|
||||||
}
|
}
|
||||||
|
@ -54,7 +55,7 @@ func (uh *UpstreamHost) Down() bool {
|
||||||
func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
|
||||||
for _, upstream := range p.Upstreams {
|
for _, upstream := range p.Upstreams {
|
||||||
if middleware.Path(r.URL.Path).Matches(upstream.From()) {
|
if middleware.Path(r.URL.Path).Matches(upstream.from()) {
|
||||||
var replacer middleware.Replacer
|
var replacer middleware.Replacer
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
requestHost := r.Host
|
requestHost := r.Host
|
||||||
|
@ -119,14 +120,3 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
|
||||||
return p.Next.ServeHTTP(w, r)
|
return p.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new instance of proxy middleware.
|
|
||||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
|
||||||
if upstreams, err := newStaticUpstreams(c); err == nil {
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
|
||||||
return Proxy{Next: next, Upstreams: upstreams}
|
|
||||||
}, nil
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type staticUpstream struct {
|
type StaticUpstream struct {
|
||||||
from string
|
From string
|
||||||
Hosts HostPool
|
Hosts HostPool
|
||||||
Policy Policy
|
Policy Policy
|
||||||
|
|
||||||
|
@ -24,127 +20,11 @@ type staticUpstream struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStaticUpstreams(c middleware.Controller) ([]Upstream, error) {
|
func (u *StaticUpstream) from() string {
|
||||||
var upstreams []Upstream
|
return u.From
|
||||||
|
|
||||||
for c.Next() {
|
|
||||||
upstream := &staticUpstream{
|
|
||||||
from: "",
|
|
||||||
Hosts: nil,
|
|
||||||
Policy: &Random{},
|
|
||||||
FailTimeout: 10 * time.Second,
|
|
||||||
MaxFails: 1,
|
|
||||||
}
|
|
||||||
var proxyHeaders http.Header
|
|
||||||
if !c.Args(&upstream.from) {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
to := c.RemainingArgs()
|
|
||||||
if len(to) == 0 {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.NextBlock() {
|
|
||||||
switch c.Val() {
|
|
||||||
case "policy":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
switch c.Val() {
|
|
||||||
case "random":
|
|
||||||
upstream.Policy = &Random{}
|
|
||||||
case "round_robin":
|
|
||||||
upstream.Policy = &RoundRobin{}
|
|
||||||
case "least_conn":
|
|
||||||
upstream.Policy = &LeastConn{}
|
|
||||||
default:
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
case "fail_timeout":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
if dur, err := time.ParseDuration(c.Val()); err == nil {
|
|
||||||
upstream.FailTimeout = dur
|
|
||||||
} else {
|
|
||||||
return upstreams, err
|
|
||||||
}
|
|
||||||
case "max_fails":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
if n, err := strconv.Atoi(c.Val()); err == nil {
|
|
||||||
upstream.MaxFails = int32(n)
|
|
||||||
} else {
|
|
||||||
return upstreams, err
|
|
||||||
}
|
|
||||||
case "health_check":
|
|
||||||
if !c.NextArg() {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
upstream.HealthCheck.Path = c.Val()
|
|
||||||
upstream.HealthCheck.Interval = 30 * time.Second
|
|
||||||
if c.NextArg() {
|
|
||||||
if dur, err := time.ParseDuration(c.Val()); err == nil {
|
|
||||||
upstream.HealthCheck.Interval = dur
|
|
||||||
} else {
|
|
||||||
return upstreams, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "proxy_header":
|
|
||||||
var header, value string
|
|
||||||
if !c.Args(&header, &value) {
|
|
||||||
return upstreams, c.ArgErr()
|
|
||||||
}
|
|
||||||
if proxyHeaders == nil {
|
|
||||||
proxyHeaders = make(map[string][]string)
|
|
||||||
}
|
|
||||||
proxyHeaders.Add(header, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream.Hosts = make([]*UpstreamHost, len(to))
|
|
||||||
for i, host := range to {
|
|
||||||
if !strings.HasPrefix(host, "http") {
|
|
||||||
host = "http://" + host
|
|
||||||
}
|
|
||||||
uh := &UpstreamHost{
|
|
||||||
Name: host,
|
|
||||||
Conns: 0,
|
|
||||||
Fails: 0,
|
|
||||||
FailTimeout: upstream.FailTimeout,
|
|
||||||
Unhealthy: false,
|
|
||||||
ExtraHeaders: proxyHeaders,
|
|
||||||
CheckDown: func(upstream *staticUpstream) UpstreamHostDownFunc {
|
|
||||||
return func(uh *UpstreamHost) bool {
|
|
||||||
if uh.Unhealthy {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if uh.Fails >= upstream.MaxFails &&
|
|
||||||
upstream.MaxFails != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}(upstream),
|
|
||||||
}
|
|
||||||
if baseUrl, err := url.Parse(uh.Name); err == nil {
|
|
||||||
uh.ReverseProxy = NewSingleHostReverseProxy(baseUrl)
|
|
||||||
} else {
|
|
||||||
return upstreams, err
|
|
||||||
}
|
|
||||||
upstream.Hosts[i] = uh
|
|
||||||
}
|
|
||||||
|
|
||||||
if upstream.HealthCheck.Path != "" {
|
|
||||||
go upstream.healthCheckWorker(nil)
|
|
||||||
}
|
|
||||||
upstreams = append(upstreams, upstream)
|
|
||||||
}
|
|
||||||
return upstreams, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) healthCheck() {
|
func (u *StaticUpstream) healthCheck() {
|
||||||
for _, host := range u.Hosts {
|
for _, host := range u.Hosts {
|
||||||
hostUrl := host.Name + u.HealthCheck.Path
|
hostUrl := host.Name + u.HealthCheck.Path
|
||||||
if r, err := http.Get(hostUrl); err == nil {
|
if r, err := http.Get(hostUrl); err == nil {
|
||||||
|
@ -157,7 +37,7 @@ func (u *staticUpstream) healthCheck() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) healthCheckWorker(stop chan struct{}) {
|
func (u *StaticUpstream) HealthCheckWorker(stop chan struct{}) {
|
||||||
ticker := time.NewTicker(u.HealthCheck.Interval)
|
ticker := time.NewTicker(u.HealthCheck.Interval)
|
||||||
u.healthCheck()
|
u.healthCheck()
|
||||||
for {
|
for {
|
||||||
|
@ -172,11 +52,7 @@ func (u *staticUpstream) healthCheckWorker(stop chan struct{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *staticUpstream) From() string {
|
func (u *StaticUpstream) Select() *UpstreamHost {
|
||||||
return u.from
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *staticUpstream) Select() *UpstreamHost {
|
|
||||||
pool := u.Hosts
|
pool := u.Hosts
|
||||||
if len(pool) == 1 {
|
if len(pool) == 1 {
|
||||||
if pool[0].Down() {
|
if pool[0].Down() {
|
||||||
|
|
50
server/config.go
Normal file
50
server/config.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config configuration for a single server.
|
||||||
|
type Config struct {
|
||||||
|
// The hostname or IP on which to serve
|
||||||
|
Host string
|
||||||
|
|
||||||
|
// The port to listen on
|
||||||
|
Port string
|
||||||
|
|
||||||
|
// The directory from which to serve files
|
||||||
|
Root string
|
||||||
|
|
||||||
|
// HTTPS configuration
|
||||||
|
TLS TLSConfig
|
||||||
|
|
||||||
|
// Middleware stack; map of path scope to middleware -- TODO: Support path scope?
|
||||||
|
Middleware map[string][]middleware.Middleware
|
||||||
|
|
||||||
|
// Functions (or methods) to execute at server start; these
|
||||||
|
// are executed before any parts of the server are configured,
|
||||||
|
// and the functions are blocking
|
||||||
|
Startup []func() error
|
||||||
|
|
||||||
|
// Functions (or methods) to execute when the server quits;
|
||||||
|
// these are executed in response to SIGINT and are blocking
|
||||||
|
Shutdown []func() error
|
||||||
|
|
||||||
|
// The path to the configuration file from which this was loaded
|
||||||
|
ConfigFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the host:port of c as a string.
|
||||||
|
func (c Config) Address() string {
|
||||||
|
return net.JoinHostPort(c.Host, c.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig describes how TLS should be configured and used,
|
||||||
|
// if at all. A certificate and key are both required.
|
||||||
|
type TLSConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
Certificate string
|
||||||
|
Key string
|
||||||
|
}
|
Loading…
Reference in a new issue