mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-27 12:25:55 +03: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},
|
||||
{"ext", setup.Ext},
|
||||
{"basicauth", setup.BasicAuth},
|
||||
//{"proxy", setup.Proxy},
|
||||
// {"fastcgi", setup.FastCGI},
|
||||
{"proxy", setup.Proxy},
|
||||
{"fastcgi", setup.FastCGI},
|
||||
// {"websocket", setup.WebSocket},
|
||||
// {"markdown", setup.Markdown},
|
||||
// {"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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -15,30 +14,6 @@ import (
|
|||
"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.
|
||||
type Handler struct {
|
||||
Next middleware.Handler
|
||||
|
@ -222,78 +197,6 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, path string) (map[string]s
|
|||
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.
|
||||
type Rule struct {
|
||||
// The base path to match. Required.
|
||||
|
|
|
@ -3,11 +3,12 @@ package proxy
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
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
|
||||
// suitable upstream host, or nil if no such hosts are available.
|
||||
type Upstream interface {
|
||||
// The path this upstream host should be routed on
|
||||
From() string
|
||||
//The path this upstream host should be routed on
|
||||
from() string
|
||||
// Selects an upstream host to be routed to.
|
||||
Select() *UpstreamHost
|
||||
}
|
||||
|
@ -54,7 +55,7 @@ func (uh *UpstreamHost) Down() bool {
|
|||
func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
|
||||
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
|
||||
start := time.Now()
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type staticUpstream struct {
|
||||
from string
|
||||
type StaticUpstream struct {
|
||||
From string
|
||||
Hosts HostPool
|
||||
Policy Policy
|
||||
|
||||
|
@ -24,127 +20,11 @@ type staticUpstream struct {
|
|||
}
|
||||
}
|
||||
|
||||
func newStaticUpstreams(c middleware.Controller) ([]Upstream, error) {
|
||||
var upstreams []Upstream
|
||||
|
||||
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) from() string {
|
||||
return u.From
|
||||
}
|
||||
|
||||
func (u *staticUpstream) healthCheck() {
|
||||
func (u *StaticUpstream) healthCheck() {
|
||||
for _, host := range u.Hosts {
|
||||
hostUrl := host.Name + u.HealthCheck.Path
|
||||
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)
|
||||
u.healthCheck()
|
||||
for {
|
||||
|
@ -172,11 +52,7 @@ func (u *staticUpstream) healthCheckWorker(stop chan struct{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (u *staticUpstream) From() string {
|
||||
return u.from
|
||||
}
|
||||
|
||||
func (u *staticUpstream) Select() *UpstreamHost {
|
||||
func (u *StaticUpstream) Select() *UpstreamHost {
|
||||
pool := u.Hosts
|
||||
if len(pool) == 1 {
|
||||
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