mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-13 09:08:50 +01:00
Major refactoring; more modular middleware
This commit is contained in:
parent
7b3d005662
commit
24fc2ae59e
15 changed files with 752 additions and 572 deletions
152
config/config.go
152
config/config.go
|
@ -2,20 +2,30 @@
|
||||||
// launching specially-configured server instances.
|
// launching specially-configured server instances.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHost = "localhost"
|
||||||
|
defaultPort = "8080"
|
||||||
|
defaultRoot = "."
|
||||||
|
)
|
||||||
|
|
||||||
// Load loads a configuration file, parses it,
|
// Load loads a configuration file, parses it,
|
||||||
// and returns a slice of Config structs which
|
// and returns a slice of Config structs which
|
||||||
// can be used to create and configure server
|
// can be used to create and configure server
|
||||||
// instances.
|
// instances.
|
||||||
func Load(filename string) ([]Config, error) {
|
func Load(filename string) ([]Config, error) {
|
||||||
p := parser{}
|
file, err := os.Open(filename)
|
||||||
err := p.lexer.Load(filename)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer p.lexer.Close()
|
defer file.Close()
|
||||||
return p.Parse()
|
p := newParser(file)
|
||||||
|
return p.parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNotFound returns whether or not the error is
|
// IsNotFound returns whether or not the error is
|
||||||
|
@ -41,21 +51,15 @@ func Default() []Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
// config represents a server configuration. It
|
// config represents a server configuration. It
|
||||||
// is populated by parsing a config file. (Use
|
// is populated by parsing a config file (via the
|
||||||
// the Load function.)
|
// Load function).
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
Root string
|
Root string
|
||||||
Gzip bool
|
|
||||||
RequestLog Log
|
|
||||||
ErrorLog Log
|
|
||||||
Rewrites []Rewrite
|
|
||||||
Redirects []Redirect
|
|
||||||
Extensions []string
|
|
||||||
ErrorPages map[int]string // Map of HTTP status code to filename
|
|
||||||
Headers []Headers
|
|
||||||
TLS TLSConfig
|
TLS TLSConfig
|
||||||
|
Middleware []middleware.Middleware
|
||||||
|
Startup []func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns the host:port of c as a string.
|
// Address returns the host:port of c as a string.
|
||||||
|
@ -63,38 +67,6 @@ func (c Config) Address() string {
|
||||||
return c.Host + ":" + c.Port
|
return c.Host + ":" + c.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite describes an internal location rewrite.
|
|
||||||
type Rewrite struct {
|
|
||||||
From string
|
|
||||||
To string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect describes an HTTP redirect.
|
|
||||||
type Redirect struct {
|
|
||||||
From string
|
|
||||||
To string
|
|
||||||
Code int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log represents the settings for a log.
|
|
||||||
type Log struct {
|
|
||||||
Enabled bool
|
|
||||||
OutputFile string
|
|
||||||
Format string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers groups a slice of HTTP headers by a URL pattern.
|
|
||||||
type Headers struct {
|
|
||||||
Url string
|
|
||||||
Headers []Header
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header represents a single HTTP header, simply a name and value.
|
|
||||||
type Header struct {
|
|
||||||
Name string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSConfig describes how TLS should be configured and used,
|
// TLSConfig describes how TLS should be configured and used,
|
||||||
// if at all. At least a certificate and key are required.
|
// if at all. At least a certificate and key are required.
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
|
@ -102,89 +74,3 @@ type TLSConfig struct {
|
||||||
Certificate string
|
Certificate string
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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,
|
|
||||||
"306": 306,
|
|
||||||
"307": 307,
|
|
||||||
"308": 308,
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpErrors is a list of supported HTTP error codes.
|
|
||||||
var httpErrors = map[string]int{
|
|
||||||
"400": 400,
|
|
||||||
"401": 401,
|
|
||||||
"402": 402,
|
|
||||||
"403": 403,
|
|
||||||
"404": 404,
|
|
||||||
"405": 405,
|
|
||||||
"406": 406,
|
|
||||||
"407": 407,
|
|
||||||
"408": 408,
|
|
||||||
"409": 409,
|
|
||||||
"410": 410,
|
|
||||||
"411": 411,
|
|
||||||
"412": 412,
|
|
||||||
"413": 413,
|
|
||||||
"414": 414,
|
|
||||||
"415": 415,
|
|
||||||
"416": 416,
|
|
||||||
"417": 417,
|
|
||||||
"418": 418,
|
|
||||||
"419": 419,
|
|
||||||
"420": 420,
|
|
||||||
"422": 422,
|
|
||||||
"423": 423,
|
|
||||||
"424": 424,
|
|
||||||
"426": 426,
|
|
||||||
"428": 428,
|
|
||||||
"429": 429,
|
|
||||||
"431": 431,
|
|
||||||
"440": 440,
|
|
||||||
"444": 444,
|
|
||||||
"449": 449,
|
|
||||||
"450": 450,
|
|
||||||
"451": 451,
|
|
||||||
"494": 494,
|
|
||||||
"495": 495,
|
|
||||||
"496": 496,
|
|
||||||
"497": 497,
|
|
||||||
"498": 498,
|
|
||||||
"499": 499,
|
|
||||||
"500": 500,
|
|
||||||
"501": 501,
|
|
||||||
"502": 502,
|
|
||||||
"503": 503,
|
|
||||||
"504": 504,
|
|
||||||
"505": 505,
|
|
||||||
"506": 506,
|
|
||||||
"507": 507,
|
|
||||||
"508": 508,
|
|
||||||
"509": 509,
|
|
||||||
"510": 510,
|
|
||||||
"511": 511,
|
|
||||||
"520": 520,
|
|
||||||
"521": 521,
|
|
||||||
"522": 522,
|
|
||||||
"523": 523,
|
|
||||||
"524": 524,
|
|
||||||
"598": 598,
|
|
||||||
"599": 599,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultHost = "localhost"
|
|
||||||
defaultPort = "8080"
|
|
||||||
defaultRoot = "."
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultRequestsLog = "requests.log"
|
|
||||||
DefaultErrorsLog = "errors.log"
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
// dirFunc is a type of parsing function which processes
|
// dirFunc is a type of parsing function which processes
|
||||||
// a particular directive and populates the config.
|
// a particular directive and populates the config.
|
||||||
type dirFunc func(*parser) error
|
type dirFunc func(*parser) error
|
||||||
|
@ -15,23 +17,23 @@ func init() {
|
||||||
// invokes a method that uses this map.
|
// invokes a method that uses this map.
|
||||||
validDirectives = map[string]dirFunc{
|
validDirectives = map[string]dirFunc{
|
||||||
"root": func(p *parser) error {
|
"root": func(p *parser) error {
|
||||||
if !p.lexer.NextArg() {
|
if !p.nextArg() {
|
||||||
return p.argErr()
|
return p.argErr()
|
||||||
}
|
}
|
||||||
p.cfg.Root = p.tkn()
|
p.cfg.Root = p.tkn()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"import": func(p *parser) error {
|
"import": func(p *parser) error {
|
||||||
if !p.lexer.NextArg() {
|
if !p.nextArg() {
|
||||||
return p.argErr()
|
return p.argErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
p2 := parser{}
|
file, err := os.Open(p.tkn())
|
||||||
err := p2.lexer.Load(p.tkn())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p.err("Parse", err.Error())
|
return p.err("Parse", err.Error())
|
||||||
}
|
}
|
||||||
defer p2.lexer.Close()
|
defer file.Close()
|
||||||
|
p2 := newParser(file)
|
||||||
|
|
||||||
p2.cfg = p.cfg
|
p2.cfg = p.cfg
|
||||||
err = p2.directives()
|
err = p2.directives()
|
||||||
|
@ -42,210 +44,15 @@ func init() {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
"gzip": func(p *parser) error {
|
|
||||||
p.cfg.Gzip = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"log": func(p *parser) error {
|
|
||||||
log := Log{Enabled: true}
|
|
||||||
|
|
||||||
// Get the type of log (requests, errors, etc.)
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
logWhat := p.tkn()
|
|
||||||
|
|
||||||
// Set the log output file
|
|
||||||
if p.lexer.NextArg() {
|
|
||||||
log.OutputFile = p.tkn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the log output format
|
|
||||||
if p.lexer.NextArg() {
|
|
||||||
log.Format = p.tkn()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch logWhat {
|
|
||||||
case "requests":
|
|
||||||
if log.OutputFile == "" || log.OutputFile == "_" {
|
|
||||||
log.OutputFile = DefaultRequestsLog
|
|
||||||
}
|
|
||||||
p.cfg.RequestLog = log
|
|
||||||
case "errors":
|
|
||||||
if log.OutputFile == "" || log.OutputFile == "_" {
|
|
||||||
log.OutputFile = DefaultErrorsLog
|
|
||||||
}
|
|
||||||
p.cfg.ErrorLog = log
|
|
||||||
default:
|
|
||||||
return p.err("Parse", "Unknown log '"+logWhat+"'")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"rewrite": func(p *parser) error {
|
|
||||||
var rw Rewrite
|
|
||||||
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
rw.From = p.tkn()
|
|
||||||
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
rw.To = p.tkn()
|
|
||||||
|
|
||||||
p.cfg.Rewrites = append(p.cfg.Rewrites, rw)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"redir": func(p *parser) error {
|
|
||||||
var redir Redirect
|
|
||||||
|
|
||||||
// From
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
redir.From = p.tkn()
|
|
||||||
|
|
||||||
// To
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
redir.To = p.tkn()
|
|
||||||
|
|
||||||
// Status Code
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
if code, ok := httpRedirs[p.tkn()]; !ok {
|
|
||||||
return p.err("Parse", "Invalid redirect code '"+p.tkn()+"'")
|
|
||||||
} else {
|
|
||||||
redir.Code = code
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cfg.Redirects = append(p.cfg.Redirects, redir)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"ext": func(p *parser) error {
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
p.cfg.Extensions = append(p.cfg.Extensions, p.tkn())
|
|
||||||
for p.lexer.NextArg() {
|
|
||||||
p.cfg.Extensions = append(p.cfg.Extensions, p.tkn())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"error": func(p *parser) error {
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
if code, ok := httpErrors[p.tkn()]; !ok {
|
|
||||||
return p.err("Syntax", "Invalid error code '"+p.tkn()+"'")
|
|
||||||
} else if val, exists := p.cfg.ErrorPages[code]; exists {
|
|
||||||
return p.err("Config", p.tkn()+" error page already configured to be '"+val+"'")
|
|
||||||
} else {
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
p.cfg.ErrorPages[code] = p.tkn()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"header": func(p *parser) error {
|
|
||||||
var head Headers
|
|
||||||
var isNewPattern bool
|
|
||||||
|
|
||||||
if !p.lexer.NextArg() {
|
|
||||||
return p.argErr()
|
|
||||||
}
|
|
||||||
pattern := p.tkn()
|
|
||||||
|
|
||||||
// See if we already have a definition for this URL pattern...
|
|
||||||
for _, h := range p.cfg.Headers {
|
|
||||||
if h.Url == pattern {
|
|
||||||
head = h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...otherwise, this is a new pattern
|
|
||||||
if head.Url == "" {
|
|
||||||
head.Url = pattern
|
|
||||||
isNewPattern = true
|
|
||||||
}
|
|
||||||
|
|
||||||
processHeaderBlock := func() error {
|
|
||||||
err := p.openCurlyBrace()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for p.lexer.Next() {
|
|
||||||
if p.tkn() == "}" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h := Header{Name: p.tkn()}
|
|
||||||
if p.lexer.NextArg() {
|
|
||||||
h.Value = p.tkn()
|
|
||||||
}
|
|
||||||
head.Headers = append(head.Headers, h)
|
|
||||||
}
|
|
||||||
err = p.closeCurlyBrace()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// A single header could be declared on the same line, or
|
|
||||||
// multiple headers can be grouped by URL pattern, so we have
|
|
||||||
// to look for both here.
|
|
||||||
if p.lexer.NextArg() {
|
|
||||||
if p.tkn() == "{" {
|
|
||||||
err := processHeaderBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
h := Header{Name: p.tkn()}
|
|
||||||
if p.lexer.NextArg() {
|
|
||||||
h.Value = p.tkn()
|
|
||||||
}
|
|
||||||
head.Headers = append(head.Headers, h)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Okay, it might be an opening curly brace on the next line
|
|
||||||
if !p.lexer.Next() {
|
|
||||||
return p.eofErr()
|
|
||||||
}
|
|
||||||
err := processHeaderBlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNewPattern {
|
|
||||||
p.cfg.Headers = append(p.cfg.Headers, head)
|
|
||||||
} else {
|
|
||||||
for i := 0; i < len(p.cfg.Headers); i++ {
|
|
||||||
if p.cfg.Headers[i].Url == pattern {
|
|
||||||
p.cfg.Headers[i] = head
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
"tls": func(p *parser) error {
|
"tls": func(p *parser) error {
|
||||||
tls := TLSConfig{Enabled: true}
|
tls := TLSConfig{Enabled: true}
|
||||||
|
|
||||||
if !p.lexer.NextArg() {
|
if !p.nextArg() {
|
||||||
return p.argErr()
|
return p.argErr()
|
||||||
}
|
}
|
||||||
tls.Certificate = p.tkn()
|
tls.Certificate = p.tkn()
|
||||||
|
|
||||||
if !p.lexer.NextArg() {
|
if !p.nextArg() {
|
||||||
return p.argErr()
|
return p.argErr()
|
||||||
}
|
}
|
||||||
tls.Key = p.tkn()
|
tls.Key = p.tkn()
|
||||||
|
|
169
config/dispenser.go
Normal file
169
config/dispenser.go
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dispenser is a type that gets exposed to middleware
|
||||||
|
// generators so that they can parse tokens to configure
|
||||||
|
// their instance.
|
||||||
|
type dispenser struct {
|
||||||
|
parser *parser
|
||||||
|
iter int
|
||||||
|
tokens []token
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDispenser returns a new dispenser.
|
||||||
|
func newDispenser(p *parser) *dispenser {
|
||||||
|
d := new(dispenser)
|
||||||
|
d.iter = -1
|
||||||
|
d.parser = p
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next loads the next token. Returns true if a token
|
||||||
|
// was loaded; false otherwise. If false, all tokens
|
||||||
|
// have been consumed.
|
||||||
|
// TODO: Have the other Next functions call this one...?
|
||||||
|
func (d *dispenser) Next() bool {
|
||||||
|
if d.iter >= len(d.tokens)-1 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
d.iter++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.iter < 0 {
|
||||||
|
d.iter++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if d.iter >= len(d.tokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d.iter < len(d.tokens)-1 &&
|
||||||
|
d.tokens[d.iter].line == d.tokens[d.iter+1].line {
|
||||||
|
d.iter++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Keep this method? It's like NextArg
|
||||||
|
// but only gets the next token if it's on the next line...
|
||||||
|
func (d *dispenser) NextLine() bool {
|
||||||
|
if d.iter < 0 {
|
||||||
|
d.iter++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if d.iter >= len(d.tokens) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if d.iter < len(d.tokens)-1 &&
|
||||||
|
d.tokens[d.iter].line < d.tokens[d.iter+1].line {
|
||||||
|
d.iter++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenCurlyBrace asserts that the current token is
|
||||||
|
// an opening curly brace "{". If it isn't, an error
|
||||||
|
// is produced and false is returned.
|
||||||
|
func (d *dispenser) OpenCurlyBrace() bool {
|
||||||
|
if d.Val() == "{" {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
d.Err("Parse", "Expected '{'")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseCurlyBrace asserts that the current token is
|
||||||
|
// a closing curly brace "}". If it isn't, an error
|
||||||
|
// is produced and false is returned.
|
||||||
|
func (d *dispenser) CloseCurlyBrace() bool {
|
||||||
|
if d.Val() == "}" {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
d.Err("Parse", "Expected '}'")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val gets the text of the current token.
|
||||||
|
func (d *dispenser) Val() string {
|
||||||
|
if d.iter >= len(d.tokens) || d.iter < 0 {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return d.tokens[d.iter].text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgErr generates an argument error, meaning that another
|
||||||
|
// argument was expected but not found. The error is saved
|
||||||
|
// within the dispenser, but this function returns nil for
|
||||||
|
// convenience.
|
||||||
|
func (d *dispenser) ArgErr() middleware.Middleware {
|
||||||
|
if d.Val() == "{" {
|
||||||
|
d.Err("Syntax", "Unexpected token '{', expecting argument for directive")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
d.Err("Syntax", "Unexpected line break after '"+d.tokens[d.iter].text+"' (missing arguments?)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err generates a custom error of type kind and with a message
|
||||||
|
// of msg. The kind should be capitalized. This function returns
|
||||||
|
// nil for convenience, but loads the error into the dispenser
|
||||||
|
// so it can be reported immediately.
|
||||||
|
func (d *dispenser) Err(kind, msg string) middleware.Middleware {
|
||||||
|
msg = fmt.Sprintf("%s:%d - %s error: %s", d.parser.filename, d.tokens[d.iter].line, kind, msg)
|
||||||
|
d.err = errors.New(msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (d *dispenser) Args(targets ...*string) {
|
||||||
|
i := 0
|
||||||
|
for d.NextArg() {
|
||||||
|
*targets[i] = d.Val()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Startup registers a function to execute when the server starts.
|
||||||
|
func (d *dispenser) Startup(fn func() error) {
|
||||||
|
d.parser.cfg.Startup = append(d.parser.cfg.Startup, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the server root file path.
|
||||||
|
func (d *dispenser) Root() string {
|
||||||
|
if d.parser.cfg.Root == "" {
|
||||||
|
return "."
|
||||||
|
} else {
|
||||||
|
return d.parser.cfg.Root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host returns the hostname the server is bound to.
|
||||||
|
func (d *dispenser) Host() string {
|
||||||
|
return d.parser.cfg.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port returns the port that the server is listening on.
|
||||||
|
func (d *dispenser) Port() string {
|
||||||
|
return d.parser.cfg.Port
|
||||||
|
}
|
|
@ -3,58 +3,35 @@ package config
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lexer is a utility which can get values, token by
|
// lexer is a utility which can get values, token by
|
||||||
// token, from a config file. A token is a word, and tokens
|
// token, from a reader. A token is a word, and tokens
|
||||||
// are separated by whitespace. A word can be enclosed in
|
// are separated by whitespace. A word can be enclosed in
|
||||||
// quotes if it contains whitespace.
|
// quotes if it contains whitespace.
|
||||||
type lexer struct {
|
type lexer struct {
|
||||||
file *os.File
|
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
token token
|
token token
|
||||||
line int
|
line int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load opens a file and prepares to scan the file.
|
// load prepares the lexer to scan a file for tokens.
|
||||||
func (l *lexer) Load(filename string) error {
|
func (l *lexer) load(file io.Reader) error {
|
||||||
f, err := os.Open(filename)
|
l.reader = bufio.NewReader(file)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
l.reader = bufio.NewReader(f)
|
|
||||||
l.file = f
|
|
||||||
l.line = 1
|
l.line = 1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the file.
|
// next loads the next token into the lexer.
|
||||||
func (l *lexer) Close() {
|
// A token is delimited by whitespace, unless
|
||||||
l.file.Close()
|
// the token starts with a quotes character (")
|
||||||
}
|
// in which case the token goes until the closing
|
||||||
|
// quotes (the enclosing quotes are not included).
|
||||||
// Next gets the next token from the input. The resulting token
|
// The rest of the line is skipped if a "#"
|
||||||
// is in l.token if next returns true. If Next returns false,
|
// character is read in. Returns true if a token
|
||||||
// there are no more tokens.
|
// was loaded; false otherwise.
|
||||||
func (l *lexer) Next() bool {
|
func (l *lexer) next() bool {
|
||||||
return l.next(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextArg works just like Next, but returns false if the next
|
|
||||||
// token is not on the same line as the one before. This method
|
|
||||||
// makes it easier to throw syntax errors when more values are
|
|
||||||
// expected on the same line.
|
|
||||||
func (l *lexer) NextArg() bool {
|
|
||||||
return l.next(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// next gets the next token according to newlineOK, which
|
|
||||||
// specifies whether it's OK if the next token is on another
|
|
||||||
// line. Returns true if there was a new token loaded, false
|
|
||||||
// otherwise.
|
|
||||||
func (l *lexer) next(newlineOK bool) bool {
|
|
||||||
var val []rune
|
var val []rune
|
||||||
var comment, quoted, escaped bool
|
var comment, quoted, escaped bool
|
||||||
|
|
||||||
|
@ -99,21 +76,15 @@ func (l *lexer) next(newlineOK bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if unicode.IsSpace(ch) {
|
if unicode.IsSpace(ch) {
|
||||||
|
if ch == '\r' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
l.line++
|
l.line++
|
||||||
comment = false
|
comment = false
|
||||||
}
|
}
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
return makeToken()
|
return makeToken()
|
||||||
} else if !newlineOK {
|
|
||||||
err := l.reader.UnreadRune()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if ch == '\n' {
|
|
||||||
l.line--
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -138,8 +109,7 @@ func (l *lexer) next(newlineOK bool) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A token represents a single valuable/processable unit
|
// token represents a single processable unit.
|
||||||
// in a config file.
|
|
||||||
type token struct {
|
type token struct {
|
||||||
line int
|
line int
|
||||||
text string
|
text string
|
||||||
|
|
109
config/parser.go
109
config/parser.go
|
@ -3,34 +3,121 @@ package config
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parser is a type which can parse config files.
|
// parser is a type which can parse config files.
|
||||||
type parser struct {
|
type parser struct {
|
||||||
lexer lexer
|
filename string // the name of the file that we're parsing
|
||||||
cfg Config
|
lexer lexer // the lexer that is giving us tokens from the raw input
|
||||||
|
cfg Config // each server gets one Config; this is the one we're currently building
|
||||||
|
other map[string]*dispenser // tokens to be parsed later by others (middleware generators)
|
||||||
|
unused bool // sometimes the token won't be immediately consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
// newParser makes a new parser and prepares it for parsing, given
|
||||||
|
// the input to parse.
|
||||||
|
func newParser(file *os.File) *parser {
|
||||||
|
p := &parser{filename: file.Name()}
|
||||||
|
p.lexer.load(file)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses the configuration file. It produces a slice of Config
|
// Parse parses the configuration file. It produces a slice of Config
|
||||||
// structs which can be used to create and configure server instances.
|
// structs which can be used to create and configure server instances.
|
||||||
func (p *parser) Parse() ([]Config, error) {
|
func (p *parser) parse() ([]Config, error) {
|
||||||
var configs []Config
|
var configs []Config
|
||||||
|
|
||||||
for p.lexer.Next() {
|
for p.lexer.next() {
|
||||||
p.cfg = Config{ErrorPages: make(map[int]string)}
|
err := p.parseOne()
|
||||||
|
|
||||||
err := p.parse()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return configs, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
configs = append(configs, p.cfg)
|
configs = append(configs, p.cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return configs, nil
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nextArg loads the next token if it is on the same line.
|
||||||
|
// Returns true if a token was loaded; false otherwise.
|
||||||
|
func (p *parser) nextArg() bool {
|
||||||
|
if p.unused {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
line := p.line()
|
||||||
|
if p.next() {
|
||||||
|
if p.line() > line {
|
||||||
|
p.unused = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// next loads the next token and returns true if a token
|
||||||
|
// was loaded; false otherwise.
|
||||||
|
func (p *parser) next() bool {
|
||||||
|
if p.unused {
|
||||||
|
p.unused = false
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return p.lexer.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOne parses the contents of a configuration
|
||||||
|
// file for a single Config object (each server or
|
||||||
|
// virtualhost instance gets their own Config struct),
|
||||||
|
// which is until the next address/server block.
|
||||||
|
// Call this only after you know that the lexer has another
|
||||||
|
// another token and you're not in the middle of a server
|
||||||
|
// block already.
|
||||||
|
func (p *parser) parseOne() error {
|
||||||
|
p.cfg = Config{}
|
||||||
|
|
||||||
|
p.other = make(map[string]*dispenser)
|
||||||
|
|
||||||
|
err := p.begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.unwrap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap gets the middleware generators from the middleware
|
||||||
|
// package in the order in which they are registered, and
|
||||||
|
// executes the top-level functions (the generator function)
|
||||||
|
// to expose the second layers which is the actual middleware.
|
||||||
|
// This function should be called only after p has filled out
|
||||||
|
// p.other and that the entire server block has been consumed.
|
||||||
|
func (p *parser) unwrap() error {
|
||||||
|
for _, directive := range middleware.Ordered() {
|
||||||
|
if disp, ok := p.other[directive]; ok {
|
||||||
|
if generator, ok := middleware.GetGenerator(directive); ok {
|
||||||
|
mid := generator(disp)
|
||||||
|
if mid == nil {
|
||||||
|
return disp.err
|
||||||
|
}
|
||||||
|
p.cfg.Middleware = append(p.cfg.Middleware, mid)
|
||||||
|
} else {
|
||||||
|
return errors.New("No middleware bound to directive '" + directive + "'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// tkn is shorthand to get the text/value of the current token.
|
// tkn is shorthand to get the text/value of the current token.
|
||||||
func (p *parser) tkn() string {
|
func (p *parser) tkn() string {
|
||||||
return p.lexer.token.text
|
return p.lexer.token.text
|
||||||
|
@ -58,10 +145,10 @@ func (p *parser) eofErr() error {
|
||||||
return p.err("Syntax", "Unexpected EOF")
|
return p.err("Syntax", "Unexpected EOF")
|
||||||
}
|
}
|
||||||
|
|
||||||
// err creates a "{{kind}} error: ..." with a custom message msg. The
|
// err creates an error with a custom message msg: "{{kind}} error: {{msg}}". The
|
||||||
// file name and line number are included in the error message.
|
// file name and line number are included in the error message.
|
||||||
func (p *parser) err(kind, msg string) error {
|
func (p *parser) err(kind, msg string) error {
|
||||||
msg = fmt.Sprintf("%s error: %s:%d - %s", kind, p.lexer.file.Name(), p.line(), msg)
|
msg = fmt.Sprintf("%s:%d - %s error: %s", p.filename, p.line(), kind, msg)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "github.com/mholt/caddy/middleware"
|
||||||
|
|
||||||
// This file contains the recursive-descent parsing
|
// This file contains the recursive-descent parsing
|
||||||
// functions.
|
// functions.
|
||||||
|
|
||||||
// parse is the top of the recursive-descent parsing.
|
// begin is the top of the recursive-descent parsing.
|
||||||
// It parses at most 1 server configuration (an address
|
// It parses at most one server configuration (an address
|
||||||
// and its directives).
|
// and its directives).
|
||||||
func (p *parser) parse() error {
|
func (p *parser) begin() error {
|
||||||
err := p.address()
|
err := p.address()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -23,15 +25,23 @@ func (p *parser) parse() error {
|
||||||
// address expects that the current token is a host:port
|
// address expects that the current token is a host:port
|
||||||
// combination.
|
// combination.
|
||||||
func (p *parser) address() error {
|
func (p *parser) address() error {
|
||||||
|
if p.tkn() == "}" || p.tkn() == "{" {
|
||||||
|
return p.err("Syntax", "'"+p.tkn()+"' is not a listening address or EOF")
|
||||||
|
}
|
||||||
p.cfg.Host, p.cfg.Port = parseAddress(p.tkn())
|
p.cfg.Host, p.cfg.Port = parseAddress(p.tkn())
|
||||||
p.lexer.Next()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addressBlock leads into parsing directives. It
|
// addressBlock leads into parsing directives, including
|
||||||
// handles directives enclosed by curly braces and
|
// possible opening/closing curly braces around the block.
|
||||||
|
// It handles directives enclosed by curly braces and
|
||||||
// directives not enclosed by curly braces.
|
// directives not enclosed by curly braces.
|
||||||
func (p *parser) addressBlock() error {
|
func (p *parser) addressBlock() error {
|
||||||
|
if !p.next() {
|
||||||
|
// file consisted of only an address
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err := p.openCurlyBrace()
|
err := p.openCurlyBrace()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// meh, single-server configs don't need curly braces
|
// meh, single-server configs don't need curly braces
|
||||||
|
@ -51,7 +61,9 @@ func (p *parser) addressBlock() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// openCurlyBrace expects the current token to be an
|
// openCurlyBrace expects the current token to be an
|
||||||
// opening curly brace.
|
// opening curly brace. This acts like an assertion
|
||||||
|
// because it returns an error if the token is not
|
||||||
|
// a opening curly brace.
|
||||||
func (p *parser) openCurlyBrace() error {
|
func (p *parser) openCurlyBrace() error {
|
||||||
if p.tkn() != "{" {
|
if p.tkn() != "{" {
|
||||||
return p.syntaxErr("{")
|
return p.syntaxErr("{")
|
||||||
|
@ -60,6 +72,8 @@ func (p *parser) openCurlyBrace() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeCurlyBrace expects the current token to be
|
// 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.
|
// a closing curly brace.
|
||||||
func (p *parser) closeCurlyBrace() error {
|
func (p *parser) closeCurlyBrace() error {
|
||||||
if p.tkn() != "}" {
|
if p.tkn() != "}" {
|
||||||
|
@ -73,18 +87,67 @@ func (p *parser) closeCurlyBrace() error {
|
||||||
// directive. It goes until EOF or closing curly
|
// directive. It goes until EOF or closing curly
|
||||||
// brace.
|
// brace.
|
||||||
func (p *parser) directives() error {
|
func (p *parser) directives() error {
|
||||||
for p.lexer.Next() {
|
for p.next() {
|
||||||
if p.tkn() == "}" {
|
if p.tkn() == "}" {
|
||||||
|
// end of address scope
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if fn, ok := validDirectives[p.tkn()]; !ok {
|
if fn, ok := validDirectives[p.tkn()]; ok {
|
||||||
return p.syntaxErr("[directive]")
|
|
||||||
} else {
|
|
||||||
err := fn(p)
|
err := fn(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if middleware.Registered(p.tkn()) {
|
||||||
|
err := p.collectTokens()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return p.err("Syntax", "Unexpected token '"+p.tkn()+"', expecting a valid directive")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collectTokens consumes tokens until the directive's scope
|
||||||
|
// closes (either end of line or end of curly brace block).
|
||||||
|
func (p *parser) collectTokens() error {
|
||||||
|
directive := p.tkn()
|
||||||
|
line := p.line()
|
||||||
|
nesting := 0
|
||||||
|
breakOk := false
|
||||||
|
disp := newDispenser(p)
|
||||||
|
|
||||||
|
// Re-use a duplicate directive's dispenser from before
|
||||||
|
// (the parsing logic in the middleware generator must
|
||||||
|
// account for multiple occurrences of its directive, even
|
||||||
|
// if that means returning an error or overwriting settings)
|
||||||
|
if existing, ok := p.other[directive]; ok {
|
||||||
|
disp = existing
|
||||||
|
}
|
||||||
|
|
||||||
|
// The directive is appended as a relevant token
|
||||||
|
disp.tokens = append(disp.tokens, p.lexer.token)
|
||||||
|
|
||||||
|
for p.next() {
|
||||||
|
if p.tkn() == "{" {
|
||||||
|
nesting++
|
||||||
|
} else if p.line() > line && nesting == 0 {
|
||||||
|
p.unused = true
|
||||||
|
breakOk = true
|
||||||
|
break
|
||||||
|
} else if p.tkn() == "}" && nesting > 0 {
|
||||||
|
nesting--
|
||||||
|
} else if p.tkn() == "}" && nesting == 0 {
|
||||||
|
return p.err("Syntax", "Unexpected '}' because no matching open curly brace '{'")
|
||||||
|
}
|
||||||
|
disp.tokens = append(disp.tokens, p.lexer.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !breakOk || nesting > 0 {
|
||||||
|
return p.eofErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.other[directive] = disp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
12
main.go
12
main.go
|
@ -9,6 +9,12 @@ import (
|
||||||
"github.com/mholt/caddy/server"
|
"github.com/mholt/caddy/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var conf string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&conf, "conf", server.DefaultConfigFile, "the configuration file to use")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
@ -40,9 +46,3 @@ func main() {
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.StringVar(&conf, "conf", server.DefaultConfigFile, "the configuration file to use")
|
|
||||||
}
|
|
||||||
|
|
||||||
var conf string
|
|
||||||
|
|
|
@ -10,11 +10,24 @@ import (
|
||||||
// passed in as well as possible extensions to add, internally,
|
// passed in as well as possible extensions to add, internally,
|
||||||
// to paths requested. The first path+ext that matches a resource
|
// to paths requested. The first path+ext that matches a resource
|
||||||
// that exists will be used.
|
// that exists will be used.
|
||||||
func Extensionless(root string, extensions []string) Middleware {
|
func Extensionless(p parser) Middleware {
|
||||||
|
var extensions []string
|
||||||
|
var root = p.Root() // TODO: Big gotcha! Save this now before it goes away! We can't get this later during a request!
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
extensions = append(extensions, p.Val())
|
||||||
|
for p.NextArg() {
|
||||||
|
extensions = append(extensions, p.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resourceExists := func(path string) bool {
|
resourceExists := func(path string) bool {
|
||||||
_, err := os.Stat(root + path)
|
_, err := os.Stat(root + path)
|
||||||
// technically we should use os.IsNotExist(err)
|
// technically we should use os.IsNotExist(err)
|
||||||
// but we don't handle any other error types anyway
|
// but we don't handle any other kinds of errors anyway
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,20 +7,19 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adapted from https://gist.github.com/the42/1956518
|
func Gzip(p parser) Middleware {
|
||||||
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
// Gzip is middleware that gzip-compresses the response.
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
func Gzip(next http.HandlerFunc) http.HandlerFunc {
|
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
next(w, r)
|
||||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
return
|
||||||
next(w, r)
|
}
|
||||||
return
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
|
gzipWriter := gzip.NewWriter(w)
|
||||||
|
defer gzipWriter.Close()
|
||||||
|
gz := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}
|
||||||
|
next(gz, r)
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
|
||||||
gzipWriter := gzip.NewWriter(w)
|
|
||||||
defer gzipWriter.Close()
|
|
||||||
gz := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}
|
|
||||||
next(gz, r)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,5 +35,6 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
if w.Header().Get("Content-Type") == "" {
|
if w.Header().Get("Content-Type") == "" {
|
||||||
w.Header().Set("Content-Type", http.DetectContentType(b))
|
w.Header().Set("Content-Type", http.DetectContentType(b))
|
||||||
}
|
}
|
||||||
return w.Writer.Write(b)
|
n, err := w.Writer.Write(b)
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,109 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Headers is middleware that adds headers to the responses
|
// Headers is middleware that adds headers to the responses
|
||||||
// for requests matching a certain path.
|
// for requests matching a certain path.
|
||||||
func Headers(headers []config.Headers) Middleware {
|
func Headers(p parser) Middleware {
|
||||||
|
type (
|
||||||
|
// Header represents a single HTTP header, simply a name and value.
|
||||||
|
header struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers groups a slice of HTTP headers by a URL pattern.
|
||||||
|
headers struct {
|
||||||
|
Url string
|
||||||
|
Headers []header
|
||||||
|
}
|
||||||
|
)
|
||||||
|
var rules []headers
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
var head headers
|
||||||
|
var isNewPattern bool
|
||||||
|
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
pattern := p.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
|
||||||
|
}
|
||||||
|
|
||||||
|
processHeaderBlock := func() bool {
|
||||||
|
if !p.OpenCurlyBrace() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for p.Next() {
|
||||||
|
if p.Val() == "}" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h := header{Name: p.Val()}
|
||||||
|
if p.NextArg() {
|
||||||
|
h.Value = p.Val()
|
||||||
|
}
|
||||||
|
head.Headers = append(head.Headers, h)
|
||||||
|
}
|
||||||
|
if !p.CloseCurlyBrace() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single header could be declared on the same line, or
|
||||||
|
// multiple headers can be grouped by URL pattern, so we have
|
||||||
|
// to look for both here.
|
||||||
|
if p.NextArg() {
|
||||||
|
if p.Val() == "{" {
|
||||||
|
if !processHeaderBlock() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h := header{Name: p.Val()}
|
||||||
|
if p.NextArg() {
|
||||||
|
h.Value = p.Val()
|
||||||
|
}
|
||||||
|
head.Headers = append(head.Headers, h)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Okay, it might be an opening curly brace on the next line
|
||||||
|
if !p.Next() {
|
||||||
|
return p.Err("Parse", "Unexpected EOF")
|
||||||
|
}
|
||||||
|
if !processHeaderBlock() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNewPattern {
|
||||||
|
rules = append(rules, head)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(rules); i++ {
|
||||||
|
if rules[i].Url == pattern {
|
||||||
|
rules[i] = head
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, rule := range headers {
|
for _, rule := range rules {
|
||||||
if pathsMatch(r.URL.Path, rule.Url) {
|
if Path(r.URL.Path).Matches(rule.Url) {
|
||||||
for _, header := range rule.Headers {
|
for _, header := range rule.Headers {
|
||||||
w.Header().Set(header.Name, header.Value)
|
w.Header().Set(header.Name, header.Value)
|
||||||
}
|
}
|
||||||
|
@ -23,13 +113,3 @@ func Headers(headers []config.Headers) Middleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether or not p1 and p2 are matching
|
|
||||||
// paths. This can be defined a number of ways
|
|
||||||
// and it is not for sure yet how to match URL/path
|
|
||||||
// strings. It may be a prefix match or a full
|
|
||||||
// string match, it may strip trailing slashes.
|
|
||||||
// Until the software hits 1.0, this will be in flux.
|
|
||||||
func pathsMatch(p1, p2 string) bool {
|
|
||||||
return strings.HasPrefix(p1, p2)
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,12 +3,52 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RequestLog(logger *log.Logger, format string) Middleware {
|
func RequestLog(p parser) Middleware {
|
||||||
if format == "" {
|
var logWhat, outputFile, format string
|
||||||
format = defaultReqLogFormat
|
var logger *log.Logger
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
p.Args(&logWhat, &outputFile, &format)
|
||||||
|
|
||||||
|
if logWhat == "" {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
if outputFile == "" {
|
||||||
|
outputFile = defaultLogFilename
|
||||||
|
}
|
||||||
|
switch format {
|
||||||
|
case "":
|
||||||
|
format = defaultReqLogFormat
|
||||||
|
case "{common}":
|
||||||
|
format = commonLogFormat
|
||||||
|
case "{combined}":
|
||||||
|
format = combinedLogFormat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open the log file for writing when the server starts
|
||||||
|
p.Startup(func() error {
|
||||||
|
var err error
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
if outputFile == "stdout" {
|
||||||
|
file = os.Stdout
|
||||||
|
} else if outputFile == "stderr" {
|
||||||
|
file = os.Stderr
|
||||||
|
} else {
|
||||||
|
file, err = os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = log.New(file, "", 0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
sw := newResponseRecorder(w)
|
sw := newResponseRecorder(w)
|
||||||
|
@ -19,24 +59,9 @@ func RequestLog(logger *log.Logger, format string) Middleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO.
|
|
||||||
func ErrorLog(logger *log.Logger, format string) Middleware {
|
|
||||||
if format == "" {
|
|
||||||
format = defaultErrLogFormat
|
|
||||||
}
|
|
||||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
sw := newResponseRecorder(w)
|
|
||||||
next(sw, r)
|
|
||||||
// This is still TODO -- we need to define what constitutes an error to be logged
|
|
||||||
//logger.Println("TODO")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
defaultLogFilename = "access.log"
|
||||||
commonLogFormat = `{remote} ` + emptyStringReplacer + ` [{time}] "{method} {uri} {proto}" {status} {size}`
|
commonLogFormat = `{remote} ` + emptyStringReplacer + ` [{time}] "{method} {uri} {proto}" {status} {size}`
|
||||||
combinedLogFormat = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
|
combinedLogFormat = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
|
||||||
defaultReqLogFormat = commonLogFormat
|
defaultReqLogFormat = commonLogFormat
|
||||||
defaultErrLogFormat = "[TODO]"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,10 +2,96 @@
|
||||||
// the servers to use, according to their configuration.
|
// the servers to use, according to their configuration.
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Middleware is a type of function that generates a new
|
// This init function registers middleware. Register middleware
|
||||||
// layer of middleware. It is imperative that the HandlerFunc
|
// in the order they should be executed during a request.
|
||||||
// being passed in is executed by the middleware, otherwise
|
// Middlewares execute in an order like A-B-C-C-B-A.
|
||||||
// part of the stack will not be called.
|
func init() {
|
||||||
type Middleware func(http.HandlerFunc) http.HandlerFunc
|
register("gzip", Gzip)
|
||||||
|
register("header", Headers)
|
||||||
|
register("log", RequestLog)
|
||||||
|
register("rewrite", Rewrite)
|
||||||
|
register("redir", Redirect)
|
||||||
|
register("ext", Extensionless)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Generator represents the outer layer of a middleware that
|
||||||
|
// parses tokens to configure the middleware instance.
|
||||||
|
Generator func(parser) Middleware
|
||||||
|
|
||||||
|
// Middleware is the middle layer which represents the traditional
|
||||||
|
// idea of middleware: it is passed the next HandlerFunc in the chain
|
||||||
|
// and returns the inner layer, which is the actual HandlerFunc.
|
||||||
|
Middleware func(http.HandlerFunc) http.HandlerFunc
|
||||||
|
|
||||||
|
// parser is the type which middleware generators use to access
|
||||||
|
// tokens and other information they need to configure the instance.
|
||||||
|
parser interface {
|
||||||
|
Next() bool
|
||||||
|
NextArg() bool
|
||||||
|
NextLine() bool
|
||||||
|
Val() string
|
||||||
|
OpenCurlyBrace() bool
|
||||||
|
CloseCurlyBrace() bool
|
||||||
|
ArgErr() Middleware
|
||||||
|
Err(string, string) Middleware
|
||||||
|
Args(...*string)
|
||||||
|
Startup(func() error)
|
||||||
|
Root() string
|
||||||
|
Host() string
|
||||||
|
Port() string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// registry stores the registered middleware:
|
||||||
|
// both the order and the directives to which they
|
||||||
|
// are bound.
|
||||||
|
registry = struct {
|
||||||
|
directiveMap map[string]Generator
|
||||||
|
order []string
|
||||||
|
}{
|
||||||
|
directiveMap: make(map[string]Generator),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGenerator gets the generator function (outer layer)
|
||||||
|
// of a middleware, according to the directive passed in.
|
||||||
|
func GetGenerator(directive string) (Generator, bool) {
|
||||||
|
rm, ok := registry.directiveMap[directive]
|
||||||
|
return rm, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// register binds a middleware generator (outer function)
|
||||||
|
// to a directive. Upon each request, middleware will be
|
||||||
|
// executed in the order they are registered.
|
||||||
|
func register(directive string, generator Generator) {
|
||||||
|
registry.directiveMap[directive] = generator
|
||||||
|
registry.order = append(registry.order, directive)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordered returns the ordered list of registered directives.
|
||||||
|
func Ordered() []string {
|
||||||
|
return registry.order
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registered returns whether or not a directive is registered.
|
||||||
|
func Registered(directive string) bool {
|
||||||
|
_, ok := GetGenerator(directive)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path represents a URI path, maybe with pattern characters.
|
||||||
|
type Path string
|
||||||
|
|
||||||
|
// Path matching will probably not always be a direct
|
||||||
|
// comparison; this method assures that paths can be
|
||||||
|
// easily matched.
|
||||||
|
func (p Path) Matches(other string) bool {
|
||||||
|
return strings.HasPrefix(string(p), other)
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,52 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Redirect is middleware for redirecting certain requests
|
// Redirect is middleware for redirecting certain requests
|
||||||
// to other locations.
|
// to other locations.
|
||||||
func Redirect(redirs []config.Redirect) Middleware {
|
func Redirect(p parser) Middleware {
|
||||||
|
|
||||||
|
// Redirect describes an HTTP redirect rule.
|
||||||
|
type redirect struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Code int
|
||||||
|
}
|
||||||
|
|
||||||
|
var redirects []redirect
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
var rule redirect
|
||||||
|
|
||||||
|
// From
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
rule.From = p.Val()
|
||||||
|
|
||||||
|
// To
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
rule.To = p.Val()
|
||||||
|
|
||||||
|
// Status Code
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, ok := httpRedirs[p.Val()]; !ok {
|
||||||
|
return p.Err("Parse", "Invalid redirect code '"+p.Val()+"'")
|
||||||
|
} else {
|
||||||
|
rule.Code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects = append(redirects, rule)
|
||||||
|
}
|
||||||
|
|
||||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, rule := range redirs {
|
for _, rule := range redirects {
|
||||||
if r.URL.Path == rule.From {
|
if r.URL.Path == rule.From {
|
||||||
http.Redirect(w, r, rule.To, rule.Code)
|
http.Redirect(w, r, rule.To, rule.Code)
|
||||||
break
|
break
|
||||||
|
@ -21,3 +56,16 @@ func Redirect(redirs []config.Redirect) Middleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
"306": 306,
|
||||||
|
"307": 307,
|
||||||
|
"308": 308,
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,35 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rewrite is middleware for rewriting requests internally to
|
// Rewrite is middleware for rewriting requests internally to
|
||||||
// a different path.
|
// a different path.
|
||||||
func Rewrite(rewrites []config.Rewrite) Middleware {
|
func Rewrite(p parser) Middleware {
|
||||||
|
|
||||||
|
// Rewrite describes an internal location rewrite rule.
|
||||||
|
type rewrite struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
}
|
||||||
|
|
||||||
|
var rewrites []rewrite
|
||||||
|
|
||||||
|
for p.Next() {
|
||||||
|
var rule rewrite
|
||||||
|
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
rule.From = p.Val()
|
||||||
|
|
||||||
|
if !p.NextArg() {
|
||||||
|
return p.ArgErr()
|
||||||
|
}
|
||||||
|
rule.To = p.Val()
|
||||||
|
|
||||||
|
rewrites = append(rewrites, rule)
|
||||||
|
}
|
||||||
|
|
||||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, rule := range rewrites {
|
for _, rule := range rewrites {
|
||||||
|
|
101
server/server.go
101
server/server.go
|
@ -4,13 +4,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/config"
|
"github.com/mholt/caddy/config"
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// servers maintains a registry of running servers.
|
// The default configuration file to load if none is specified
|
||||||
|
const DefaultConfigFile = "Caddyfile"
|
||||||
|
|
||||||
|
// servers maintains a registry of running servers, keyed by address.
|
||||||
var servers = make(map[string]*Server)
|
var servers = make(map[string]*Server)
|
||||||
|
|
||||||
// Server represents an instance of a server, which serves
|
// Server represents an instance of a server, which serves
|
||||||
|
@ -46,7 +48,7 @@ func New(conf config.Config) (*Server, error) {
|
||||||
|
|
||||||
// Serve starts the server. It blocks until the server quits.
|
// Serve starts the server. It blocks until the server quits.
|
||||||
func (s *Server) Serve() error {
|
func (s *Server) Serve() error {
|
||||||
err := s.configureStack()
|
err := s.buildStack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -73,73 +75,20 @@ func (s *Server) Log(v ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureStack builds the server's middleware stack based
|
// buildStack builds the server's middleware stack based
|
||||||
// on its config. This method should be called last before
|
// on its config. This method should be called last before
|
||||||
// ListenAndServe begins.
|
// ListenAndServe begins.
|
||||||
func (s *Server) configureStack() error {
|
func (s *Server) buildStack() error {
|
||||||
var mid []middleware.Middleware
|
s.fileServer = http.FileServer(http.Dir(s.config.Root))
|
||||||
var err error
|
|
||||||
conf := s.config
|
|
||||||
|
|
||||||
// FileServer is the main application layer
|
for _, start := range s.config.Startup {
|
||||||
s.fileServer = http.FileServer(http.Dir(conf.Root))
|
err := start()
|
||||||
|
if err != nil {
|
||||||
// push prepends each middleware to the stack so the
|
return err
|
||||||
// compilation can iterate them in a natural, increasing order
|
|
||||||
push := func(m middleware.Middleware) {
|
|
||||||
mid = append(mid, nil)
|
|
||||||
copy(mid[1:], mid[0:])
|
|
||||||
mid[0] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
// BEGIN ADDING MIDDLEWARE
|
|
||||||
// Middleware will be executed in the order they're added.
|
|
||||||
|
|
||||||
if conf.RequestLog.Enabled {
|
|
||||||
if conf.RequestLog.Enabled {
|
|
||||||
s.reqlog, err = enableLogging(conf.RequestLog)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
push(middleware.RequestLog(s.reqlog, conf.RequestLog.Format))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.ErrorLog.Enabled {
|
s.compile(s.config.Middleware)
|
||||||
if conf.ErrorLog.Enabled {
|
|
||||||
s.errlog, err = enableLogging(conf.ErrorLog)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
push(middleware.ErrorLog(s.errlog, conf.ErrorLog.Format))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(conf.Rewrites) > 0 {
|
|
||||||
push(middleware.Rewrite(conf.Rewrites))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(conf.Redirects) > 0 {
|
|
||||||
push(middleware.Redirect(conf.Redirects))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(conf.Extensions) > 0 {
|
|
||||||
push(middleware.Extensionless(conf.Root, conf.Extensions))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(conf.Headers) > 0 {
|
|
||||||
push(middleware.Headers(conf.Headers))
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Gzip {
|
|
||||||
push(middleware.Gzip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// END ADDING MIDDLEWARE
|
|
||||||
|
|
||||||
// Compiling the middleware unwraps each HandlerFunc,
|
|
||||||
// fully configured, ready to serve every request.
|
|
||||||
s.compile(mid)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -152,27 +101,3 @@ func (s *Server) compile(layers []middleware.Middleware) {
|
||||||
s.stack = layer(s.stack)
|
s.stack = layer(s.stack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableLogging opens a log file and keeps it open for the lifetime
|
|
||||||
// of the server. In fact, the log file is never closed as long as
|
|
||||||
// the program is running, since the server will be running for
|
|
||||||
// that long. If that ever changes, the log file should be closed.
|
|
||||||
func enableLogging(l config.Log) (*log.Logger, error) {
|
|
||||||
var file *os.File
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if l.OutputFile == "stdout" {
|
|
||||||
file = os.Stdout
|
|
||||||
} else if l.OutputFile == "stderr" {
|
|
||||||
file = os.Stderr
|
|
||||||
} else {
|
|
||||||
file, err = os.OpenFile(l.OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return log.New(file, "", 0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const DefaultConfigFile = "Caddyfile"
|
|
||||||
|
|
Loading…
Reference in a new issue