mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 18:30:54 +08:00
Bringing in latest from master; refactoring under way
This commit is contained in:
parent
5f32f9b1c8
commit
995edf0566
155
config/config.go
155
config/config.go
|
@ -1,13 +1,13 @@
|
|||
// Package config contains utilities and types necessary for
|
||||
// launching specially-configured server instances.
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/mholt/caddy/config/parse"
|
||||
"github.com/mholt/caddy/config/setup"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"github.com/mholt/caddy/server"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -19,110 +19,89 @@ const (
|
|||
DefaultConfigFile = "Caddyfile"
|
||||
)
|
||||
|
||||
// Host and Port are configurable via command line flag
|
||||
// These three defaults are configurable through the command line
|
||||
var (
|
||||
Root = DefaultRoot
|
||||
Host = DefaultHost
|
||||
Port = DefaultPort
|
||||
)
|
||||
|
||||
// config represents a server configuration. It
|
||||
// is populated by parsing a config file (via the
|
||||
// Load function).
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Load loads a configuration file, parses it,
|
||||
// and returns a slice of Config structs which
|
||||
// can be used to create and configure server
|
||||
// instances.
|
||||
func Load(filename string) ([]Config, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
func Load(filename string, input io.Reader) ([]server.Config, error) {
|
||||
var configs []server.Config
|
||||
|
||||
// turn off timestamp for parsing
|
||||
flags := log.Flags()
|
||||
log.SetFlags(0)
|
||||
|
||||
p, err := newParser(file)
|
||||
serverBlocks, err := parse.ServerBlocks(filename, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return configs, err
|
||||
}
|
||||
|
||||
cfgs, err := p.parse()
|
||||
if err != nil {
|
||||
return []Config{}, err
|
||||
}
|
||||
// Each server block represents a single server/address.
|
||||
// Iterate each server block and make a config for each one,
|
||||
// executing the directives that were parsed.
|
||||
for _, sb := range serverBlocks {
|
||||
config := server.Config{
|
||||
Host: sb.Host,
|
||||
Port: sb.Port,
|
||||
Middleware: make(map[string][]middleware.Middleware),
|
||||
}
|
||||
|
||||
for i := 0; i < len(cfgs); i++ {
|
||||
cfgs[i].ConfigFile = filename
|
||||
// It is crucial that directives are executed in the proper order.
|
||||
for _, dir := range directiveOrder {
|
||||
// Execute directive if it is in the server block
|
||||
if tokens, ok := sb.Tokens[dir.name]; ok {
|
||||
// Each setup function gets a controller, which is the
|
||||
// server config and the dispenser containing only
|
||||
// this directive's tokens.
|
||||
controller := &setup.Controller{
|
||||
Config: &config,
|
||||
Dispenser: parse.NewDispenserTokens(filename, tokens),
|
||||
}
|
||||
|
||||
midware, err := dir.setup(controller)
|
||||
if err != nil {
|
||||
return configs, err
|
||||
}
|
||||
if midware != nil {
|
||||
// TODO: For now, we only support the default path scope /
|
||||
config.Middleware["/"] = append(config.Middleware["/"], midware)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Port == "" {
|
||||
config.Port = Port
|
||||
}
|
||||
|
||||
configs = append(configs, config)
|
||||
}
|
||||
|
||||
// restore logging settings
|
||||
log.SetFlags(flags)
|
||||
|
||||
return cfgs, nil
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// IsNotFound returns whether or not the error is
|
||||
// one which indicates that the configuration file
|
||||
// was not found. (Useful for checking the error
|
||||
// returned from Load).
|
||||
func IsNotFound(err error) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Default makes a default configuration
|
||||
// that's empty except for root, host, and port,
|
||||
// which are essential for serving the cwd.
|
||||
func Default() []Config {
|
||||
cfg := []Config{
|
||||
Config{
|
||||
Root: DefaultRoot,
|
||||
Host: Host,
|
||||
Port: Port,
|
||||
},
|
||||
// validDirective returns true if d is a valid
|
||||
// directive; false otherwise.
|
||||
func validDirective(d string) bool {
|
||||
for _, dir := range directiveOrder {
|
||||
if dir.name == d {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Default makes a default configuration which
|
||||
// is empty except for root, host, and port,
|
||||
// which are essentials for serving the cwd.
|
||||
func Default() server.Config {
|
||||
return server.Config{
|
||||
Root: Root,
|
||||
Host: Host,
|
||||
Port: Port,
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
package config
|
||||
|
||||
import "github.com/mholt/caddy/middleware"
|
||||
|
||||
// controller is a dispenser of tokens and also
|
||||
// facilitates setup with the server by providing
|
||||
// access to its configuration. It implements
|
||||
// the middleware.Controller interface.
|
||||
type controller struct {
|
||||
dispenser
|
||||
parser *parser
|
||||
pathScope string
|
||||
}
|
||||
|
||||
// newController returns a new controller.
|
||||
func newController(p *parser) *controller {
|
||||
return &controller{
|
||||
dispenser: dispenser{
|
||||
cursor: -1,
|
||||
filename: p.filename,
|
||||
},
|
||||
parser: p,
|
||||
}
|
||||
}
|
||||
|
||||
// Startup registers a function to execute when the server starts.
|
||||
func (c *controller) Startup(fn func() error) {
|
||||
c.parser.cfg.Startup = append(c.parser.cfg.Startup, fn)
|
||||
}
|
||||
|
||||
// Shutdown registers a function to execute when the server exits.
|
||||
func (c *controller) Shutdown(fn func() error) {
|
||||
c.parser.cfg.Shutdown = append(c.parser.cfg.Shutdown, fn)
|
||||
}
|
||||
|
||||
// Root returns the server root file path.
|
||||
func (c *controller) Root() string {
|
||||
if c.parser.cfg.Root == "" {
|
||||
return "."
|
||||
} else {
|
||||
return c.parser.cfg.Root
|
||||
}
|
||||
}
|
||||
|
||||
// Context returns the path scope that the Controller is in.
|
||||
func (c *controller) Context() middleware.Path {
|
||||
return middleware.Path(c.pathScope)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
c := newController(p)
|
||||
|
||||
if c == nil || c.parser == nil {
|
||||
t.Fatal("Expected newController to return a non-nil controller with a non-nil parser")
|
||||
}
|
||||
if c.dispenser.cursor != -1 {
|
||||
t.Errorf("Dispenser not initialized properly; expecting cursor at -1, got %d", c.dispenser.cursor)
|
||||
}
|
||||
if c.dispenser.filename != p.filename {
|
||||
t.Errorf("Dispenser's filename should be same as parser's (%s); got '%s'", p.filename, c.dispenser.filename)
|
||||
}
|
||||
|
||||
c.Startup(func() error { return nil })
|
||||
if n := len(c.parser.cfg.Startup); n != 1 {
|
||||
t.Errorf("Expected length of startup functions to be 1, got %d", n)
|
||||
}
|
||||
|
||||
if root := c.Root(); root != "." {
|
||||
t.Errorf("Expected defualt root path to be '.', got '%s'", root)
|
||||
}
|
||||
|
||||
c.parser.cfg.Root = "foobar/test"
|
||||
if root := c.Root(); root != c.parser.cfg.Root {
|
||||
t.Errorf("Expected established root path to be '%s', got '%s'", c.parser.cfg.Root, root)
|
||||
}
|
||||
|
||||
c.pathScope = "unused"
|
||||
if context := c.Context(); string(context) != c.pathScope {
|
||||
t.Errorf("Expected context to be '%s', got '%s'", c.pathScope, context)
|
||||
}
|
||||
}
|
|
@ -1,139 +1,45 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/mholt/caddy/config/parse"
|
||||
"github.com/mholt/caddy/config/setup"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// dirFunc is a type of parsing function which processes
|
||||
// a particular directive and populates the config.
|
||||
type dirFunc func(*parser) error
|
||||
|
||||
// validDirectives is a map of valid, built-in directive names
|
||||
// to their parsing function. Built-in directives cannot be
|
||||
// ordered, so they should only be used for internal server
|
||||
// configuration; not directly handling requests.
|
||||
var validDirectives map[string]dirFunc
|
||||
|
||||
func init() {
|
||||
// This has to be in the init function
|
||||
// to avoid an initialization loop error because
|
||||
// the 'import' directive (key) in this map
|
||||
// invokes a method that uses this map.
|
||||
validDirectives = map[string]dirFunc{
|
||||
"root": func(p *parser) error {
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
p.cfg.Root = p.tkn()
|
||||
|
||||
// Ensure root folder exists
|
||||
_, err := os.Stat(p.cfg.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 %s does not exist", p.cfg.Root)
|
||||
} else {
|
||||
return p.err("Path", fmt.Sprintf("Unable to access root path '%s': %s", p.cfg.Root, err.Error()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"import": func(p *parser) error {
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
|
||||
filename := p.tkn()
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return p.err("Parse", err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
p2, err := newParser(file)
|
||||
if err != nil {
|
||||
return p.err("Parse", "Could not import "+filename+"; "+err.Error())
|
||||
}
|
||||
|
||||
p2.cfg = p.cfg
|
||||
err = p2.directives()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.cfg = p2.cfg
|
||||
|
||||
return nil
|
||||
},
|
||||
"tls": func(p *parser) error {
|
||||
tls := TLSConfig{Enabled: true}
|
||||
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
tls.Certificate = p.tkn()
|
||||
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
tls.Key = p.tkn()
|
||||
|
||||
p.cfg.TLS = tls
|
||||
return nil
|
||||
},
|
||||
"startup": func(p *parser) error {
|
||||
// TODO: This code is duplicated with the shutdown directive below
|
||||
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
|
||||
command, args, err := middleware.SplitCommandAndArgs(p.tkn())
|
||||
if err != nil {
|
||||
return p.err("Parse", err.Error())
|
||||
}
|
||||
|
||||
startupfn := func() error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
p.cfg.Startup = append(p.cfg.Startup, startupfn)
|
||||
return nil
|
||||
},
|
||||
"shutdown": func(p *parser) error {
|
||||
if !p.nextArg() {
|
||||
return p.argErr()
|
||||
}
|
||||
|
||||
command, args, err := middleware.SplitCommandAndArgs(p.tkn())
|
||||
if err != nil {
|
||||
return p.err("Parse", err.Error())
|
||||
}
|
||||
|
||||
shutdownfn := func() error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
p.cfg.Shutdown = append(p.cfg.Shutdown, shutdownfn)
|
||||
return nil
|
||||
},
|
||||
// The parse package must know which directives
|
||||
// are valid, but it must not import the setup
|
||||
// or config package.
|
||||
for _, dir := range directiveOrder {
|
||||
parse.ValidDirectives[dir.name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var directiveOrder = []directive{
|
||||
{"root", setup.Root},
|
||||
{"tls", setup.TLS},
|
||||
{"startup", setup.Startup},
|
||||
{"shutdown", setup.Shutdown},
|
||||
|
||||
{"log", setup.Log},
|
||||
{"gzip", setup.Gzip},
|
||||
{"errors", setup.Errors},
|
||||
{"header", setup.Headers},
|
||||
{"rewrite", setup.Rewrite},
|
||||
{"redir", setup.Redir},
|
||||
{"ext", setup.Ext},
|
||||
{"basicauth", setup.BasicAuth},
|
||||
//{"proxy", setup.Proxy},
|
||||
// {"fastcgi", setup.FastCGI},
|
||||
// {"websocket", setup.WebSocket},
|
||||
// {"markdown", setup.Markdown},
|
||||
// {"templates", setup.Templates},
|
||||
// {"browse", setup.Browse},
|
||||
}
|
||||
|
||||
type directive struct {
|
||||
name string
|
||||
setup setupFunc
|
||||
}
|
||||
|
||||
type setupFunc func(c *setup.Controller) (middleware.Middleware, error)
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// dispenser is a type that dispenses tokens, similarly to
|
||||
// a lexer, except that it can do so with some notion of
|
||||
// structure. Its methods implement part of the
|
||||
// middleware.Controller interface, so refer to that
|
||||
// documentation for more info.
|
||||
type dispenser struct {
|
||||
filename string
|
||||
cursor int
|
||||
nesting int
|
||||
tokens []token
|
||||
}
|
||||
|
||||
// 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.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.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 (currently) 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 ""
|
||||
} else {
|
||||
return d.tokens[d.cursor].text
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Err("Wrong argument count or unexpected line ending after '" + d.Val() + "'")
|
||||
}
|
||||
|
||||
// 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.tokens[d.cursor].line, msg)
|
||||
return errors.New(msg)
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDispenser_Val_Next(t *testing.T) {
|
||||
input := `host:port
|
||||
dir1 arg1
|
||||
dir2 arg2 arg3
|
||||
dir3`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
if val := d.Val(); val != "" {
|
||||
t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
|
||||
}
|
||||
|
||||
assertNext := func(shouldLoad bool, expectedCursor int, expectedVal string) {
|
||||
if loaded := d.Next(); loaded != shouldLoad {
|
||||
t.Errorf("Next(): Expected %v but got %v instead (val '%s')", shouldLoad, loaded, d.Val())
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("Expected cursor to be %d, but was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
if d.nesting != 0 {
|
||||
t.Errorf("Nesting should be 0, was %d instead", d.nesting)
|
||||
}
|
||||
if val := d.Val(); val != expectedVal {
|
||||
t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
|
||||
}
|
||||
}
|
||||
|
||||
assertNext(true, 0, "host:port")
|
||||
assertNext(true, 1, "dir1")
|
||||
assertNext(true, 2, "arg1")
|
||||
assertNext(true, 3, "dir2")
|
||||
assertNext(true, 4, "arg2")
|
||||
assertNext(true, 5, "arg3")
|
||||
assertNext(true, 6, "dir3")
|
||||
// Note: This next test simply asserts existing behavior.
|
||||
// If desired, we may wish to empty the token value after
|
||||
// reading past the EOF. Open an issue if you want this change.
|
||||
assertNext(false, 6, "dir3")
|
||||
}
|
||||
|
||||
func TestDispenser_NextArg(t *testing.T) {
|
||||
input := `dir1 arg1
|
||||
dir2 arg2 arg3
|
||||
dir3`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
|
||||
if d.Next() != shouldLoad {
|
||||
t.Errorf("Next(): Should load token but got false instead (val: '%s')", d.Val())
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("Next(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
if val := d.Val(); val != expectedVal {
|
||||
t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
|
||||
}
|
||||
}
|
||||
|
||||
assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) {
|
||||
if d.NextArg() != true {
|
||||
t.Error("NextArg(): Should load next argument but got false instead")
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("NextArg(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
if val := d.Val(); val != expectedVal {
|
||||
t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
|
||||
}
|
||||
if !loadAnother {
|
||||
if d.NextArg() != false {
|
||||
t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val())
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("NextArg(): Expected cursor to remain at %d, but it was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertNext(true, "dir1", 0)
|
||||
assertNextArg("arg1", false, 1)
|
||||
assertNext(true, "dir2", 2)
|
||||
assertNextArg("arg2", true, 3)
|
||||
assertNextArg("arg3", false, 4)
|
||||
assertNext(true, "dir3", 5)
|
||||
assertNext(false, "dir3", 5)
|
||||
}
|
||||
|
||||
func TestDispenser_NextLine(t *testing.T) {
|
||||
input := `host:port
|
||||
dir1 arg1
|
||||
dir2 arg2 arg3`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
|
||||
if d.NextLine() != shouldLoad {
|
||||
t.Errorf("NextLine(): Should load token but got false instead (val: '%s')", d.Val())
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("NextLine(): Expected cursor to be %d, instead was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
if val := d.Val(); val != expectedVal {
|
||||
t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
|
||||
}
|
||||
}
|
||||
|
||||
assertNextLine(true, "host:port", 0)
|
||||
assertNextLine(true, "dir1", 1)
|
||||
assertNextLine(false, "dir1", 1)
|
||||
d.Next() // arg1
|
||||
assertNextLine(true, "dir2", 3)
|
||||
assertNextLine(false, "dir2", 3)
|
||||
d.Next() // arg2
|
||||
assertNextLine(false, "arg2", 4)
|
||||
d.Next() // arg3
|
||||
assertNextLine(false, "arg3", 5)
|
||||
}
|
||||
|
||||
func TestDispenser_NextBlock(t *testing.T) {
|
||||
input := `foobar1 {
|
||||
sub1 arg1
|
||||
sub2
|
||||
}
|
||||
foobar2 {
|
||||
}`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
|
||||
if loaded := d.NextBlock(); loaded != shouldLoad {
|
||||
t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
|
||||
}
|
||||
if d.cursor != expectedCursor {
|
||||
t.Errorf("NextBlock(): Expected cursor to be %d, was %d", expectedCursor, d.cursor)
|
||||
}
|
||||
if d.nesting != expectedNesting {
|
||||
t.Errorf("NextBlock(): Nesting should be %d, not %d", expectedNesting, d.nesting)
|
||||
}
|
||||
}
|
||||
|
||||
assertNextBlock(false, -1, 0)
|
||||
d.Next() // foobar1
|
||||
assertNextBlock(true, 2, 1)
|
||||
assertNextBlock(true, 3, 1)
|
||||
assertNextBlock(true, 4, 1)
|
||||
assertNextBlock(false, 5, 0)
|
||||
d.Next() // foobar2
|
||||
assertNextBlock(true, 8, 1)
|
||||
assertNextBlock(false, 8, 0)
|
||||
}
|
||||
|
||||
func TestDispenser_Args(t *testing.T) {
|
||||
var s1, s2, s3 string
|
||||
input := `dir1 arg1 arg2 arg3
|
||||
dir2 arg4 arg5
|
||||
dir3 arg6 arg7
|
||||
dir4`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
d.Next() // dir1
|
||||
|
||||
// As many strings as arguments
|
||||
if all := d.Args(&s1, &s2, &s3); !all {
|
||||
t.Error("Args(): Expected true, got false")
|
||||
}
|
||||
if s1 != "arg1" {
|
||||
t.Errorf("Args(): Expected s1 to be 'arg1', got '%s'", s1)
|
||||
}
|
||||
if s2 != "arg2" {
|
||||
t.Errorf("Args(): Expected s2 to be 'arg2', got '%s'", s2)
|
||||
}
|
||||
if s3 != "arg3" {
|
||||
t.Errorf("Args(): Expected s3 to be 'arg3', got '%s'", s3)
|
||||
}
|
||||
|
||||
d.Next() // dir2
|
||||
|
||||
// More strings than arguments
|
||||
if all := d.Args(&s1, &s2, &s3); all {
|
||||
t.Error("Args(): Expected false, got true")
|
||||
}
|
||||
if s1 != "arg4" {
|
||||
t.Errorf("Args(): Expected s1 to be 'arg4', got '%s'", s1)
|
||||
}
|
||||
if s2 != "arg5" {
|
||||
t.Errorf("Args(): Expected s2 to be 'arg5', got '%s'", s2)
|
||||
}
|
||||
if s3 != "arg3" {
|
||||
t.Errorf("Args(): Expected s3 to be unchanged ('arg3'), instead got '%s'", s3)
|
||||
}
|
||||
|
||||
// (quick cursor check just for kicks and giggles)
|
||||
if d.cursor != 6 {
|
||||
t.Errorf("Cursor should be 6, but is %d", d.cursor)
|
||||
}
|
||||
|
||||
d.Next() // dir3
|
||||
|
||||
// More arguments than strings
|
||||
if all := d.Args(&s1); !all {
|
||||
t.Error("Args(): Expected true, got false")
|
||||
}
|
||||
if s1 != "arg6" {
|
||||
t.Errorf("Args(): Expected s1 to be 'arg6', got '%s'", s1)
|
||||
}
|
||||
|
||||
d.Next() // dir4
|
||||
|
||||
// No arguments or strings
|
||||
if all := d.Args(); !all {
|
||||
t.Error("Args(): Expected true, got false")
|
||||
}
|
||||
|
||||
// No arguments but at least one string
|
||||
if all := d.Args(&s1); all {
|
||||
t.Error("Args(): Expected false, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispenser_RemainingArgs(t *testing.T) {
|
||||
input := `dir1 arg1 arg2 arg3
|
||||
dir2 arg4 arg5
|
||||
dir3 arg6 { arg7
|
||||
dir4`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
d.Next() // dir1
|
||||
|
||||
args := d.RemainingArgs()
|
||||
if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(args, expected) {
|
||||
t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
|
||||
}
|
||||
|
||||
d.Next() // dir2
|
||||
|
||||
args = d.RemainingArgs()
|
||||
if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) {
|
||||
t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
|
||||
}
|
||||
|
||||
d.Next() // dir3
|
||||
|
||||
args = d.RemainingArgs()
|
||||
if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) {
|
||||
t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
|
||||
}
|
||||
|
||||
d.Next() // {
|
||||
d.Next() // arg7
|
||||
d.Next() // dir4
|
||||
|
||||
args = d.RemainingArgs()
|
||||
if len(args) != 0 {
|
||||
t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispenser_ArgErr_Err(t *testing.T) {
|
||||
input := `dir1 {
|
||||
}
|
||||
dir2 arg1 arg2`
|
||||
d := makeTestDispenser("test", input)
|
||||
|
||||
d.cursor = 1 // {
|
||||
|
||||
if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "{") {
|
||||
t.Errorf("ArgErr(): Expected an error message with { in it, but got '%v'", err)
|
||||
}
|
||||
|
||||
d.cursor = 5 // arg2
|
||||
|
||||
if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "arg2") {
|
||||
t.Errorf("ArgErr(): Expected an error message with 'arg2' in it; got '%v'", err)
|
||||
}
|
||||
|
||||
err := d.Err("foobar")
|
||||
if err == nil {
|
||||
t.Fatalf("Err(): Expected an error, got nil")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "test:3") {
|
||||
t.Errorf("Expected error message with filename:line in it; got '%v'", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "foobar") {
|
||||
t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestDispenser(filename, input string) dispenser {
|
||||
return dispenser{
|
||||
filename: filename,
|
||||
cursor: -1,
|
||||
tokens: getTokens(input),
|
||||
}
|
||||
}
|
||||
|
||||
func getTokens(input string) (tokens []token) {
|
||||
var l lexer
|
||||
l.load(strings.NewReader(input))
|
||||
for l.next() {
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
root /test/imported/public_html
|
114
config/lexer.go
114
config/lexer.go
|
@ -1,114 +0,0 @@
|
|||
package config
|
||||
|
||||
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 processable unit.
|
||||
token struct {
|
||||
line int
|
||||
text string
|
||||
}
|
||||
)
|
||||
|
||||
// load prepares the lexer to scan a file for tokens.
|
||||
func (l *lexer) load(file io.Reader) error {
|
||||
l.reader = bufio.NewReader(file)
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type lexerTestCase struct {
|
||||
input string
|
||||
expected []token
|
||||
}
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
testCases := []lexerTestCase{
|
||||
{
|
||||
input: `host:123`,
|
||||
expected: []token{
|
||||
{line: 1, text: "host:123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123
|
||||
|
||||
directive`,
|
||||
expected: []token{
|
||||
{line: 1, text: "host:123"},
|
||||
{line: 3, text: "directive"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
directive
|
||||
}`,
|
||||
expected: []token{
|
||||
{line: 1, text: "host:123"},
|
||||
{line: 1, text: "{"},
|
||||
{line: 2, text: "directive"},
|
||||
{line: 3, text: "}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 { directive }`,
|
||||
expected: []token{
|
||||
{line: 1, text: "host:123"},
|
||||
{line: 1, text: "{"},
|
||||
{line: 1, text: "directive"},
|
||||
{line: 1, text: "}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
#comment
|
||||
directive
|
||||
# comment
|
||||
foobar # another comment
|
||||
}`,
|
||||
expected: []token{
|
||||
{line: 1, text: "host:123"},
|
||||
{line: 1, text: "{"},
|
||||
{line: 3, text: "directive"},
|
||||
{line: 5, text: "foobar"},
|
||||
{line: 6, text: "}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `a "quoted value" b
|
||||
foobar`,
|
||||
expected: []token{
|
||||
{line: 1, text: "a"},
|
||||
{line: 1, text: "quoted value"},
|
||||
{line: 1, text: "b"},
|
||||
{line: 2, text: "foobar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted \"value\" inside" B`,
|
||||
expected: []token{
|
||||
{line: 1, text: "A"},
|
||||
{line: 1, text: `quoted "value" inside`},
|
||||
{line: 1, text: "B"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted value with line
|
||||
break inside" {
|
||||
foobar
|
||||
}`,
|
||||
expected: []token{
|
||||
{line: 1, text: "A"},
|
||||
{line: 1, text: "quoted value with line\n\t\t\t\t\tbreak inside"},
|
||||
{line: 2, text: "{"},
|
||||
{line: 3, text: "foobar"},
|
||||
{line: 4, text: "}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "skip those\r\nCR characters",
|
||||
expected: []token{
|
||||
{line: 1, text: "skip"},
|
||||
{line: 1, text: "those"},
|
||||
{line: 2, text: "CR"},
|
||||
{line: 2, text: "characters"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actual := tokenize(testCase.input)
|
||||
lexerCompare(t, i, testCase.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func tokenize(input string) (tokens []token) {
|
||||
l := lexer{}
|
||||
l.load(strings.NewReader(input))
|
||||
for l.next() {
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lexerCompare(t *testing.T, n int, expected, actual []token) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
|
||||
}
|
||||
|
||||
for i := 0; i < len(actual) && i < len(expected); i++ {
|
||||
if actual[i].line != expected[i].line {
|
||||
t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
|
||||
n, i, expected[i].text, expected[i].line, actual[i].line)
|
||||
break
|
||||
}
|
||||
if actual[i].text != expected[i].text {
|
||||
t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
|
||||
n, i, expected[i].text, actual[i].text)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"github.com/mholt/caddy/middleware/basicauth"
|
||||
"github.com/mholt/caddy/middleware/browse"
|
||||
"github.com/mholt/caddy/middleware/errors"
|
||||
"github.com/mholt/caddy/middleware/extensions"
|
||||
"github.com/mholt/caddy/middleware/fastcgi"
|
||||
"github.com/mholt/caddy/middleware/git"
|
||||
"github.com/mholt/caddy/middleware/gzip"
|
||||
"github.com/mholt/caddy/middleware/headers"
|
||||
"github.com/mholt/caddy/middleware/log"
|
||||
"github.com/mholt/caddy/middleware/markdown"
|
||||
"github.com/mholt/caddy/middleware/proxy"
|
||||
"github.com/mholt/caddy/middleware/redirect"
|
||||
"github.com/mholt/caddy/middleware/rewrite"
|
||||
"github.com/mholt/caddy/middleware/templates"
|
||||
"github.com/mholt/caddy/middleware/websockets"
|
||||
)
|
||||
|
||||
// This init function registers middleware. Register
|
||||
// middleware in the order they should be executed
|
||||
// during a request (A, B, C...). Middleware execute
|
||||
// in the order A-B-C-*-C-B-A, assuming they call
|
||||
// the Next handler in the chain.
|
||||
//
|
||||
// Note: Ordering is VERY important. Every middleware
|
||||
// will feel the effects of all other middleware below
|
||||
// (after) them, but must not care what middleware above
|
||||
// them are doing.
|
||||
//
|
||||
// For example, log needs to know the status code and exactly
|
||||
// how many bytes were written to the client, which every
|
||||
// other middleware can affect, so it gets registered first.
|
||||
// The errors middleware does not care if gzip or log modifies
|
||||
// its response, so it gets registered below them. Gzip, on the
|
||||
// other hand, DOES care what errors does to the response since
|
||||
// it must compress every output to the client, even error pages,
|
||||
// so it must be registered before the errors middleware and any
|
||||
// others that would write to the response.
|
||||
func init() {
|
||||
register("log", log.New)
|
||||
register("gzip", gzip.New)
|
||||
register("errors", errors.New)
|
||||
register("header", headers.New)
|
||||
register("rewrite", rewrite.New)
|
||||
register("redir", redirect.New)
|
||||
register("ext", extensions.New)
|
||||
register("basicauth", basicauth.New)
|
||||
register("proxy", proxy.New)
|
||||
register("git", git.New)
|
||||
register("fastcgi", fastcgi.New)
|
||||
register("websocket", websockets.New)
|
||||
register("markdown", markdown.New)
|
||||
register("templates", templates.New)
|
||||
register("browse", browse.New)
|
||||
}
|
||||
|
||||
// registry stores the registered middleware:
|
||||
// both the order and the directives to which they
|
||||
// are bound.
|
||||
var registry = struct {
|
||||
directiveMap map[string]middleware.Generator
|
||||
ordered []string
|
||||
}{
|
||||
directiveMap: make(map[string]middleware.Generator),
|
||||
}
|
||||
|
||||
// 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 middleware.Generator) {
|
||||
registry.directiveMap[directive] = generator
|
||||
registry.ordered = append(registry.ordered, directive)
|
||||
}
|
||||
|
||||
// middlewareRegistered returns whether or not a directive is registered.
|
||||
func middlewareRegistered(directive string) bool {
|
||||
_, ok := registry.directiveMap[directive]
|
||||
return ok
|
||||
}
|
208
config/parser.go
208
config/parser.go
|
@ -1,208 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
// parser is a type which can parse config files.
|
||||
parser struct {
|
||||
filename string // the name of the file that we're parsing
|
||||
lexer lexer // the lexer that is giving us tokens from the raw input
|
||||
hosts []hostPort // the list of host:port combinations current tokens apply to
|
||||
cfg Config // each virtual host gets one Config; this is the one we're currently building
|
||||
cfgs []Config // after a Config is created, it may need to be copied for multiple hosts
|
||||
other []locationContext // tokens to be 'parsed' later by middleware generators
|
||||
scope *locationContext // the current location context (path scope) being populated
|
||||
unused *token // sometimes a token will be read but not immediately consumed
|
||||
eof bool // if we encounter a valid EOF in a hard place
|
||||
}
|
||||
|
||||
// locationContext represents a location context
|
||||
// (path block) in a config file. If no context
|
||||
// is explicitly defined, the default location
|
||||
// context is "/".
|
||||
locationContext struct {
|
||||
path string
|
||||
directives map[string]*controller
|
||||
}
|
||||
|
||||
// hostPort just keeps a hostname and port together
|
||||
hostPort struct {
|
||||
host, port string
|
||||
}
|
||||
)
|
||||
|
||||
// newParser makes a new parser and prepares it for parsing, given
|
||||
// the input to parse.
|
||||
func newParser(file *os.File) (*parser, error) {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &parser{filename: stat.Name()}
|
||||
p.lexer.load(file)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Parse parses the configuration file. It produces a slice of Config
|
||||
// structs which can be used to create and configure server instances.
|
||||
func (p *parser) parse() ([]Config, error) {
|
||||
var configs []Config
|
||||
|
||||
for p.lexer.next() {
|
||||
err := p.parseOne()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configs = append(configs, p.cfgs...)
|
||||
}
|
||||
|
||||
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 != nil {
|
||||
return false
|
||||
}
|
||||
line, tkn := p.line(), p.lexer.token
|
||||
if p.next() {
|
||||
if p.line() > line {
|
||||
p.unused = &tkn
|
||||
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 != nil {
|
||||
p.unused = nil
|
||||
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 when you know that the lexer has another
|
||||
// another token and you're not in another server
|
||||
// block already.
|
||||
func (p *parser) parseOne() error {
|
||||
p.cfgs = []Config{}
|
||||
p.cfg = Config{
|
||||
Middleware: make(map[string][]middleware.Middleware),
|
||||
}
|
||||
p.other = []locationContext{}
|
||||
|
||||
err := p.begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unwrap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make a copy of the config for each
|
||||
// address that will be using it
|
||||
for _, hostport := range p.hosts {
|
||||
cfgCopy := p.cfg
|
||||
cfgCopy.Host = hostport.host
|
||||
cfgCopy.Port = hostport.port
|
||||
p.cfgs = append(p.cfgs, cfgCopy)
|
||||
}
|
||||
|
||||
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 are the actual middleware.
|
||||
// This function should be called only after p has filled out
|
||||
// p.other and the entire server block has already been consumed.
|
||||
func (p *parser) unwrap() error {
|
||||
if len(p.other) == 0 {
|
||||
// no middlewares were invoked
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, directive := range registry.ordered {
|
||||
// TODO: For now, we only support the first and default path scope ("/", held in p.other[0])
|
||||
// but when we implement support for path scopes, we will have to change this logic
|
||||
// to loop over them and order them. We need to account for situations where multiple
|
||||
// path scopes overlap, regex (??), etc...
|
||||
if disp, ok := p.other[0].directives[directive]; ok {
|
||||
if generator, ok := registry.directiveMap[directive]; ok {
|
||||
mid, err := generator(disp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mid != nil {
|
||||
// TODO: Again, we assume the default path scope here...
|
||||
p.cfg.Middleware[p.other[0].path] = append(p.cfg.Middleware[p.other[0].path], 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.
|
||||
func (p *parser) tkn() string {
|
||||
if p.unused != nil {
|
||||
return p.unused.text
|
||||
}
|
||||
return p.lexer.token.text
|
||||
}
|
||||
|
||||
// line is shorthand to get the line number of the current token.
|
||||
func (p *parser) line() int {
|
||||
if p.unused != nil {
|
||||
return p.unused.line
|
||||
}
|
||||
return p.lexer.token.line
|
||||
}
|
||||
|
||||
// syntaxErr creates a syntax error which explains what was
|
||||
// found and expected.
|
||||
func (p *parser) syntaxErr(expected string) error {
|
||||
return p.err("Syntax", fmt.Sprintf("Unexpected token '%s', expecting '%s'", p.tkn(), expected))
|
||||
}
|
||||
|
||||
// syntaxErr creates a syntax error that explains that there
|
||||
// weren't enough arguments on the line.
|
||||
func (p *parser) argErr() error {
|
||||
return p.err("Syntax", "Unexpected line break after '"+p.tkn()+"' (missing arguments?)")
|
||||
}
|
||||
|
||||
// eofErr creates a syntax error describing an unexpected EOF.
|
||||
func (p *parser) eofErr() error {
|
||||
return p.err("Syntax", "Unexpected EOF")
|
||||
}
|
||||
|
||||
// err creates an error with a custom message msg: "{{kind}} error: {{msg}}". The
|
||||
// file name and line number are included in the error message.
|
||||
func (p *parser) err(kind, msg string) error {
|
||||
msg = fmt.Sprintf("%s:%d - %s error: %s", p.filename, p.line(), kind, msg)
|
||||
return errors.New(msg)
|
||||
}
|
|
@ -1,330 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewParser(t *testing.T) {
|
||||
filePath := "./parser_test.go"
|
||||
expected := "parser_test.go"
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
t.Fatal("Could not open file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
p, err := newParser(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if p.filename != expected {
|
||||
t.Errorf("Expected parser to have filename '%s' but had '%s'", expected, p.filename)
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
t.Error("Expected parser to not be nil, but it was")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserBasic(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
|
||||
input := `localhost:1234
|
||||
root /test/www
|
||||
tls cert.pem key.pem`
|
||||
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
conf := confs[0]
|
||||
|
||||
if conf.Host != "localhost" {
|
||||
t.Errorf("Expected host to be 'localhost', got '%s'", conf.Host)
|
||||
}
|
||||
if conf.Port != "1234" {
|
||||
t.Errorf("Expected port to be '1234', got '%s'", conf.Port)
|
||||
}
|
||||
if conf.Root != "/test/www" {
|
||||
t.Errorf("Expected root to be '/test/www', got '%s'", conf.Root)
|
||||
}
|
||||
if !conf.TLS.Enabled {
|
||||
t.Error("Expected TLS to be enabled, but it wasn't")
|
||||
}
|
||||
if conf.TLS.Certificate != "cert.pem" {
|
||||
t.Errorf("Expected TLS certificate to be 'cert.pem', got '%s'", conf.TLS.Certificate)
|
||||
}
|
||||
if conf.TLS.Key != "key.pem" {
|
||||
t.Errorf("Expected TLS server key to be 'key.pem', got '%s'", conf.TLS.Key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserBasicWithMultipleServerBlocks(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
|
||||
input := `host1.com:443 {
|
||||
root /test/www
|
||||
tls cert.pem key.pem
|
||||
}
|
||||
|
||||
host2:80 {
|
||||
root "/test/my site"
|
||||
}`
|
||||
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
if len(confs) != 2 {
|
||||
t.Fatalf("Expected 2 configurations, but got %d: %#v", len(confs), confs)
|
||||
}
|
||||
|
||||
// First server
|
||||
if confs[0].Host != "host1.com" {
|
||||
t.Errorf("Expected first host to be 'host1.com', got '%s'", confs[0].Host)
|
||||
}
|
||||
if confs[0].Port != "443" {
|
||||
t.Errorf("Expected first port to be '443', got '%s'", confs[0].Port)
|
||||
}
|
||||
if confs[0].Root != "/test/www" {
|
||||
t.Errorf("Expected first root to be '/test/www', got '%s'", confs[0].Root)
|
||||
}
|
||||
if !confs[0].TLS.Enabled {
|
||||
t.Error("Expected first TLS to be enabled, but it wasn't")
|
||||
}
|
||||
if confs[0].TLS.Certificate != "cert.pem" {
|
||||
t.Errorf("Expected first TLS certificate to be 'cert.pem', got '%s'", confs[0].TLS.Certificate)
|
||||
}
|
||||
if confs[0].TLS.Key != "key.pem" {
|
||||
t.Errorf("Expected first TLS server key to be 'key.pem', got '%s'", confs[0].TLS.Key)
|
||||
}
|
||||
|
||||
// Second server
|
||||
if confs[1].Host != "host2" {
|
||||
t.Errorf("Expected second host to be 'host2', got '%s'", confs[1].Host)
|
||||
}
|
||||
if confs[1].Port != "80" {
|
||||
t.Errorf("Expected second port to be '80', got '%s'", confs[1].Port)
|
||||
}
|
||||
if confs[1].Root != "/test/my site" {
|
||||
t.Errorf("Expected second root to be '/test/my site', got '%s'", confs[1].Root)
|
||||
}
|
||||
if confs[1].TLS.Enabled {
|
||||
t.Error("Expected second TLS to be disabled, but it was enabled")
|
||||
}
|
||||
if confs[1].TLS.Certificate != "" {
|
||||
t.Errorf("Expected second TLS certificate to be '', got '%s'", confs[1].TLS.Certificate)
|
||||
}
|
||||
if confs[1].TLS.Key != "" {
|
||||
t.Errorf("Expected second TLS server key to be '', got '%s'", confs[1].TLS.Key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserBasicWithMultipleHostsPerBlock(t *testing.T) {
|
||||
// This test is table-driven; it is expected that each
|
||||
// input string produce the same set of configs.
|
||||
for _, input := range []string{
|
||||
`host1.com host2.com:1234
|
||||
root /public_html`, // space-separated, no block
|
||||
|
||||
`host1.com, host2.com:1234
|
||||
root /public_html`, // comma-separated, no block
|
||||
|
||||
`host1.com,
|
||||
host2.com:1234
|
||||
root /public_html`, // comma-separated, newlines, no block
|
||||
|
||||
`host1.com host2.com:1234 {
|
||||
root /public_html
|
||||
}`, // space-separated, block
|
||||
|
||||
`host1.com, host2.com:1234 {
|
||||
root /public_html
|
||||
}`, // comma-separated, block
|
||||
|
||||
`host1.com,
|
||||
host2.com:1234 {
|
||||
root /public_html
|
||||
}`, // comma-separated, newlines, block
|
||||
} {
|
||||
|
||||
p := &parser{filename: "test"}
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
if len(confs) != 2 {
|
||||
t.Fatalf("Expected 2 configurations, but got %d: %#v", len(confs), confs)
|
||||
}
|
||||
|
||||
if confs[0].Host != "host1.com" {
|
||||
t.Errorf("Expected host of first conf to be 'host1.com', got '%s'", confs[0].Host)
|
||||
}
|
||||
if confs[0].Port != DefaultPort {
|
||||
t.Errorf("Expected port of first conf to be '%s', got '%s'", DefaultPort, confs[0].Port)
|
||||
}
|
||||
if confs[0].Root != "/public_html" {
|
||||
t.Errorf("Expected root of first conf to be '/public_html', got '%s'", confs[0].Root)
|
||||
}
|
||||
|
||||
if confs[1].Host != "host2.com" {
|
||||
t.Errorf("Expected host of second conf to be 'host2.com', got '%s'", confs[1].Host)
|
||||
}
|
||||
if confs[1].Port != "1234" {
|
||||
t.Errorf("Expected port of second conf to be '1234', got '%s'", confs[1].Port)
|
||||
}
|
||||
if confs[1].Root != "/public_html" {
|
||||
t.Errorf("Expected root of second conf to be '/public_html', got '%s'", confs[1].Root)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserBasicWithAlternateAddressStyles(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
input := `http://host1.com, https://host2.com,
|
||||
host3.com:http, host4.com:1234 {
|
||||
root /test/www
|
||||
}`
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
if len(confs) != 4 {
|
||||
t.Fatalf("Expected 4 configurations, but got %d: %#v", len(confs), confs)
|
||||
}
|
||||
|
||||
for _, conf := range confs {
|
||||
if conf.Root != "/test/www" {
|
||||
t.Fatalf("Expected root for conf of %s to be '/test/www', but got: %s", conf.Address(), conf.Root)
|
||||
}
|
||||
}
|
||||
|
||||
p = &parser{filename: "test"}
|
||||
input = `host:port, http://host:port, http://host, https://host:port, host`
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err = p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
if len(confs) != 5 {
|
||||
t.Fatalf("Expected 5 configurations, but got %d: %#v", len(confs), confs)
|
||||
}
|
||||
|
||||
if confs[0].Host != "host" {
|
||||
t.Errorf("Expected conf[0] Host='host', got '%#v'", confs[0])
|
||||
}
|
||||
if confs[0].Port != "port" {
|
||||
t.Errorf("Expected conf[0] Port='port', got '%#v'", confs[0])
|
||||
}
|
||||
|
||||
if confs[1].Host != "host" {
|
||||
t.Errorf("Expected conf[1] Host='host', got '%#v'", confs[1])
|
||||
}
|
||||
if confs[1].Port != "port" {
|
||||
t.Errorf("Expected conf[1] Port='port', got '%#v'", confs[1])
|
||||
}
|
||||
|
||||
if confs[2].Host != "host" {
|
||||
t.Errorf("Expected conf[2] Host='host', got '%#v'", confs[2])
|
||||
}
|
||||
if confs[2].Port != "http" {
|
||||
t.Errorf("Expected conf[2] Port='http', got '%#v'", confs[2])
|
||||
}
|
||||
|
||||
if confs[3].Host != "host" {
|
||||
t.Errorf("Expected conf[3] Host='host', got '%#v'", confs[3])
|
||||
}
|
||||
if confs[3].Port != "port" {
|
||||
t.Errorf("Expected conf[3] Port='port', got '%#v'", confs[3])
|
||||
}
|
||||
|
||||
if confs[4].Host != "host" {
|
||||
t.Errorf("Expected conf[4] Host='host', got '%#v'", confs[4])
|
||||
}
|
||||
if confs[4].Port != DefaultPort {
|
||||
t.Errorf("Expected conf[4] Port='%s', got '%#v'", DefaultPort, confs[4].Port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserImport(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
|
||||
input := `host:123
|
||||
import import_test.txt`
|
||||
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
conf := confs[0]
|
||||
|
||||
if conf.Host != "host" {
|
||||
t.Errorf("Expected host to be 'host', got '%s'", conf.Host)
|
||||
}
|
||||
if conf.Port != "123" {
|
||||
t.Errorf("Expected port to be '123', got '%s'", conf.Port)
|
||||
}
|
||||
if conf.Root != "/test/imported/public_html" {
|
||||
t.Errorf("Expected root to be '/test/imported/public_html', got '%s'", conf.Root)
|
||||
}
|
||||
if conf.TLS.Enabled {
|
||||
t.Error("Expected TLS to be disabled, but it was enabled")
|
||||
}
|
||||
if conf.TLS.Certificate != "" {
|
||||
t.Errorf("Expected TLS certificate to be '', got '%s'", conf.TLS.Certificate)
|
||||
}
|
||||
if conf.TLS.Key != "" {
|
||||
t.Errorf("Expected TLS server key to be '', got '%s'", conf.TLS.Key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserLocationContext(t *testing.T) {
|
||||
p := &parser{filename: "test"}
|
||||
|
||||
input := `host:123 {
|
||||
/scope {
|
||||
gzip
|
||||
}
|
||||
}`
|
||||
|
||||
p.lexer.load(strings.NewReader(input))
|
||||
|
||||
confs, err := p.parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no errors, but got '%s'", err)
|
||||
}
|
||||
if len(confs) != 1 {
|
||||
t.Fatalf("Expected 1 configuration, but got %d: %#v", len(confs), confs)
|
||||
}
|
||||
|
||||
if len(p.other) != 2 {
|
||||
t.Fatalf("Expected 2 path scopes, but got %d: %#v", len(p.other), p.other)
|
||||
}
|
||||
|
||||
if p.other[0].path != "/" {
|
||||
t.Fatalf("Expected first path scope to be default '/', but got %v: %#v", p.other[0].path, p.other)
|
||||
}
|
||||
if p.other[1].path != "/scope" {
|
||||
t.Fatalf("Expected first path scope to be '/scope', but got %v: %#v", p.other[0].path, p.other)
|
||||
}
|
||||
|
||||
if dir, ok := p.other[1].directives["gzip"]; !ok {
|
||||
t.Fatalf("Expected scoped directive to be gzip, but got %v: %#v", dir, p.other[1].directives)
|
||||
}
|
||||
}
|
|
@ -1,318 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This file contains the recursive-descent parsing
|
||||
// functions.
|
||||
|
||||
// begin is the top of the recursive-descent parsing.
|
||||
// It parses at most one server configuration (an address
|
||||
// and its directives).
|
||||
func (p *parser) begin() error {
|
||||
err := p.addresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.addressBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addresses expects that the current token is a
|
||||
// "scheme://host:port" combination (the "scheme://"
|
||||
// and/or ":port" portions may be omitted). If multiple
|
||||
// addresses are specified, they must be space-
|
||||
// separated on the same line, or each token must end
|
||||
// with a comma.
|
||||
func (p *parser) addresses() error {
|
||||
var expectingAnother bool
|
||||
p.hosts = []hostPort{}
|
||||
|
||||
// address gets host and port in a format accepted by net.Dial
|
||||
address := func(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
|
||||
}
|
||||
|
||||
for {
|
||||
tkn, startLine := p.tkn(), p.line()
|
||||
|
||||
// Open brace definitely indicates end of addresses
|
||||
if tkn == "{" {
|
||||
if expectingAnother {
|
||||
return p.err("Syntax", "Expected another address but had '"+tkn+"' - check for extra comma")
|
||||
}
|
||||
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 := address(tkn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.hosts = append(p.hosts, hostPort{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
|
||||
}
|
||||
|
||||
// addressBlock leads into parsing directives, including
|
||||
// possible opening/closing curly braces around the block.
|
||||
// It handles directives enclosed by curly braces and
|
||||
// directives not enclosed by curly braces. It is expected
|
||||
// that the current token is already the beginning of
|
||||
// the address block.
|
||||
func (p *parser) addressBlock() error {
|
||||
errOpenCurlyBrace := p.openCurlyBrace()
|
||||
if errOpenCurlyBrace != nil {
|
||||
// meh, single-server configs don't need curly braces
|
||||
// but we read a token and we won't consume it; mark it unused
|
||||
p.unused = &p.lexer.token
|
||||
}
|
||||
|
||||
// When we enter an address block, we also implicitly
|
||||
// enter a path block where the path is all paths ("/")
|
||||
p.other = append(p.other, locationContext{
|
||||
path: "/",
|
||||
directives: make(map[string]*controller),
|
||||
})
|
||||
p.scope = &p.other[0]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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.tkn() != "{" {
|
||||
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.tkn() != "}" {
|
||||
return p.syntaxErr("}")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// directives parses through all the directives
|
||||
// and it expects the current token to be the first
|
||||
// directive. It goes until EOF or closing curly
|
||||
// brace which ends the address block.
|
||||
func (p *parser) directives() error {
|
||||
for p.next() {
|
||||
if p.tkn() == "}" {
|
||||
// end of address scope
|
||||
break
|
||||
}
|
||||
if p.tkn()[0] == '/' || p.tkn()[0] == '*' {
|
||||
// Path scope (a.k.a. location context)
|
||||
// Starts with / ('starts with') or * ('ends with').
|
||||
|
||||
// TODO: The parser can handle the syntax (obviously), but the
|
||||
// implementation is incomplete. This is intentional,
|
||||
// until we can better decide what kind of feature set we
|
||||
// want to support and how exactly we want these location
|
||||
// scopes to work. Until this is ready, we leave this
|
||||
// syntax undocumented. Some changes will need to be
|
||||
// made in parser.go also (the unwrap function) and
|
||||
// probably in server.go when we do this... see those TODOs.
|
||||
|
||||
var scope *locationContext
|
||||
|
||||
// If the path block is a duplicate, append to existing one
|
||||
for i := 0; i < len(p.other); i++ {
|
||||
if p.other[i].path == p.tkn() {
|
||||
scope = &p.other[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, for a new path we haven't seen before, create a new context
|
||||
if scope == nil {
|
||||
scope = &locationContext{
|
||||
path: p.tkn(),
|
||||
directives: make(map[string]*controller),
|
||||
}
|
||||
}
|
||||
|
||||
// Consume the opening curly brace
|
||||
if !p.next() {
|
||||
return p.eofErr()
|
||||
}
|
||||
err := p.openCurlyBrace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use this path scope as our current context for just a moment
|
||||
p.scope = scope
|
||||
|
||||
// Consume each directive in the path block
|
||||
for p.next() {
|
||||
err := p.closeCurlyBrace()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = p.directive()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Save the new scope and put the current scope back to "/"
|
||||
p.other = append(p.other, *scope)
|
||||
p.scope = &p.other[0]
|
||||
|
||||
} else if err := p.directive(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// directive asserts that the current token is either a built-in
|
||||
// directive or a registered middleware directive; otherwise an error
|
||||
// will be returned. If it is a valid directive, tokens will be
|
||||
// collected.
|
||||
func (p *parser) directive() error {
|
||||
if fn, ok := validDirectives[p.tkn()]; ok {
|
||||
// Built-in (standard, or 'core') directive
|
||||
err := fn(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if middlewareRegistered(p.tkn()) {
|
||||
// Middleware directive
|
||||
err := p.collectTokens()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return p.err("Syntax", "Unexpected token '"+p.tkn()+"', expecting a valid directive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectTokens consumes tokens until the directive's scope
|
||||
// closes (either end of line or end of curly brace block).
|
||||
// It creates a controller which is stored in the parser for
|
||||
// later use by the middleware.
|
||||
func (p *parser) collectTokens() error {
|
||||
if p.scope == nil {
|
||||
return errors.New("Current scope cannot be nil")
|
||||
}
|
||||
|
||||
directive := p.tkn()
|
||||
line := p.line()
|
||||
nesting := 0
|
||||
cont := newController(p)
|
||||
|
||||
// Re-use a duplicate directive's controller 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.scope.directives[directive]; ok {
|
||||
cont = existing
|
||||
}
|
||||
|
||||
// The directive is appended as a relevant token
|
||||
cont.tokens = append(cont.tokens, p.lexer.token)
|
||||
|
||||
for p.next() {
|
||||
if p.tkn() == "{" {
|
||||
nesting++
|
||||
} else if p.line() > line && nesting == 0 {
|
||||
p.unused = &p.lexer.token
|
||||
break
|
||||
} else if p.tkn() == "}" && nesting > 0 {
|
||||
nesting--
|
||||
} else if p.tkn() == "}" && nesting == 0 {
|
||||
return p.err("Syntax", "Unexpected '}' because no matching opening brace")
|
||||
}
|
||||
cont.tokens = append(cont.tokens, p.lexer.token)
|
||||
}
|
||||
|
||||
if nesting > 0 {
|
||||
return p.eofErr()
|
||||
}
|
||||
|
||||
p.scope.directives[directive] = cont
|
||||
return nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package config
|
56
main.go
56
main.go
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -23,10 +25,11 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&conf, "conf", config.DefaultConfigFile, "the configuration file to use")
|
||||
flag.BoolVar(&http2, "http2", true, "enable HTTP/2 support") // TODO: temporary flag until http2 merged into std lib
|
||||
flag.BoolVar(&quiet, "quiet", false, "quiet mode (no initialization output)")
|
||||
flag.StringVar(&conf, "conf", config.DefaultConfigFile, "The configuration file to use")
|
||||
flag.BoolVar(&http2, "http2", true, "Enable HTTP/2 support") // TODO: temporary flag until http2 merged into std lib
|
||||
flag.BoolVar(&quiet, "quiet", false, "Quiet mode (no initialization output)")
|
||||
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
||||
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
|
||||
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
||||
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
||||
flag.Parse()
|
||||
|
@ -42,16 +45,9 @@ func main() {
|
|||
}
|
||||
|
||||
// Load config from file
|
||||
allConfigs, err := config.Load(conf)
|
||||
allConfigs, err := loadConfigs(conf)
|
||||
if err != nil {
|
||||
if config.IsNotFound(err) {
|
||||
allConfigs = config.Default()
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(allConfigs) == 0 {
|
||||
allConfigs = config.Default()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Group by address (virtual hosts)
|
||||
|
@ -86,13 +82,45 @@ func main() {
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
// loadConfigs loads configuration from a file.
|
||||
func loadConfigs(confPath string) ([]server.Config, error) {
|
||||
var allConfigs []server.Config
|
||||
|
||||
file, err := os.Open(confPath)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
allConfigs, err = config.Load(path.Base(confPath), file)
|
||||
if err != nil {
|
||||
return allConfigs, err
|
||||
}
|
||||
} else {
|
||||
if os.IsNotExist(err) {
|
||||
// This is only a problem if the user
|
||||
// explicitly specified a config file
|
||||
if confPath != config.DefaultConfigFile {
|
||||
return allConfigs, err
|
||||
}
|
||||
} else {
|
||||
// ... but anything else is always a problem
|
||||
return allConfigs, err
|
||||
}
|
||||
}
|
||||
|
||||
// If config file was empty or didn't exist, use default
|
||||
if len(allConfigs) == 0 {
|
||||
allConfigs = []server.Config{config.Default()}
|
||||
}
|
||||
|
||||
return allConfigs, nil
|
||||
}
|
||||
|
||||
// arrangeBindings groups configurations by their bind address. For example,
|
||||
// a server that should listen on localhost and another on 127.0.0.1 will
|
||||
// be grouped into the same address: 127.0.0.1. It will return an error
|
||||
// if the address lookup fails or if a TLS listener is configured on the
|
||||
// same address as a plaintext HTTP listener.
|
||||
func arrangeBindings(allConfigs []config.Config) (map[string][]config.Config, error) {
|
||||
addresses := make(map[string][]config.Config)
|
||||
func arrangeBindings(allConfigs []server.Config) (map[string][]server.Config, error) {
|
||||
addresses := make(map[string][]server.Config)
|
||||
|
||||
// Group configs by bind address
|
||||
for _, conf := range allConfigs {
|
||||
|
|
|
@ -7,21 +7,14 @@ import (
|
|||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// New constructs a new BasicAuth middleware instance.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
rules, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basic := BasicAuth{
|
||||
Rules: rules,
|
||||
}
|
||||
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
basic.Next = next
|
||||
return basic
|
||||
}, nil
|
||||
// BasicAuth is middleware to protect resources with a username and password.
|
||||
// Note that HTTP Basic Authentication is not secure by itself and should
|
||||
// not be used to protect important assets without HTTPS. Even then, the
|
||||
// security of HTTP Basic Auth is disputed. Use discretion when deciding
|
||||
// what to protect with BasicAuth.
|
||||
type BasicAuth struct {
|
||||
Next middleware.Handler
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
// ServeHTTP implements the middleware.Handler interface.
|
||||
|
@ -50,48 +43,6 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
|||
return a.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) ([]Rule, error) {
|
||||
var rules []Rule
|
||||
|
||||
for c.Next() {
|
||||
var rule 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.Err("Expecting only one resource per line (extra '" + 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
|
||||
}
|
||||
|
||||
// BasicAuth is middleware to protect resources with a username and password.
|
||||
// Note that HTTP Basic Authentication is not secure by itself and should
|
||||
// not be used to protect important assets without HTTPS. Even then, the
|
||||
// security of HTTP Basic Auth is disputed. Use discretion when deciding
|
||||
// what to protect with BasicAuth.
|
||||
type BasicAuth struct {
|
||||
Next middleware.Handler
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
// Rule represents a BasicAuth rule. A username and password
|
||||
// combination protect the associated resources, which are
|
||||
// file or directory paths.
|
||||
|
|
|
@ -7,45 +7,10 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// New instantiates a new instance of error-handling middleware.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
handler, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Open the log file for writing when the server starts
|
||||
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
|
||||
}
|
||||
|
||||
// ErrorHandler handles HTTP errors (or errors from other middleware).
|
||||
type ErrorHandler struct {
|
||||
Next middleware.Handler
|
||||
|
@ -113,63 +78,4 @@ func (h ErrorHandler) errorPage(w http.ResponseWriter, code int) {
|
|||
http.Error(w, defaultBody, code)
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) (*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 := &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 = defaultLogFilename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
const defaultLogFilename = "error.log"
|
||||
const DefaultLogFilename = "error.log"
|
||||
|
|
|
@ -20,13 +20,6 @@ type Gzip struct {
|
|||
Next middleware.Handler
|
||||
}
|
||||
|
||||
// New creates a new gzip middleware instance.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return Gzip{Next: next}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ServeHTTP serves a gzipped response if the client supports it.
|
||||
func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
// for requests matching a certain path.
|
||||
type Headers struct {
|
||||
Next middleware.Handler
|
||||
Rules []HeaderRule
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
// ServeHTTP implements the middleware.Handler interface and serves requests,
|
||||
|
@ -30,9 +30,9 @@ func (h Headers) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
|
|||
}
|
||||
|
||||
type (
|
||||
// HeaderRule groups a slice of HTTP headers by a URL pattern.
|
||||
// Rule groups a slice of HTTP headers by a URL pattern.
|
||||
// TODO: use http.Header type instead?
|
||||
HeaderRule struct {
|
||||
Rule struct {
|
||||
Url string
|
||||
Headers []Header
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package headers
|
||||
|
||||
import "github.com/mholt/caddy/middleware"
|
||||
|
||||
// New constructs and configures a new headers middleware instance.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
|
||||
rules, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return Headers{Next: next, Rules: rules}
|
||||
}, nil
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package headers
|
||||
|
||||
import "github.com/mholt/caddy/middleware"
|
||||
|
||||
func parse(c middleware.Controller) ([]HeaderRule, error) {
|
||||
var rules []HeaderRule
|
||||
|
||||
for c.NextLine() {
|
||||
var head HeaderRule
|
||||
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 := 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 := 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
|
||||
}
|
|
@ -4,44 +4,13 @@ package log
|
|||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// New instantiates a new instance of logging middleware.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
rules, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Open the log files for writing when the server starts
|
||||
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 Logger{Next: next, Rules: rules}
|
||||
}, nil
|
||||
type Logger struct {
|
||||
Next middleware.Handler
|
||||
Rules []LogRule
|
||||
}
|
||||
|
||||
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
|
@ -57,58 +26,6 @@ func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|||
return l.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) ([]LogRule, error) {
|
||||
var rules []LogRule
|
||||
|
||||
for c.Next() {
|
||||
args := c.RemainingArgs()
|
||||
|
||||
if len(args) == 0 {
|
||||
// Nothing specified; use defaults
|
||||
rules = append(rules, LogRule{
|
||||
PathScope: "/",
|
||||
OutputFile: defaultLogFilename,
|
||||
Format: defaultLogFormat,
|
||||
})
|
||||
} else if len(args) == 1 {
|
||||
// Only an output file specified
|
||||
rules = append(rules, LogRule{
|
||||
PathScope: "/",
|
||||
OutputFile: args[0],
|
||||
Format: defaultLogFormat,
|
||||
})
|
||||
} else {
|
||||
// Path scope, output file, and maybe a format specified
|
||||
|
||||
format := defaultLogFormat
|
||||
|
||||
if len(args) > 2 {
|
||||
switch args[2] {
|
||||
case "{common}":
|
||||
format = commonLogFormat
|
||||
case "{combined}":
|
||||
format = combinedLogFormat
|
||||
default:
|
||||
format = args[2]
|
||||
}
|
||||
}
|
||||
|
||||
rules = append(rules, LogRule{
|
||||
PathScope: args[0],
|
||||
OutputFile: args[1],
|
||||
Format: format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
Next middleware.Handler
|
||||
Rules []LogRule
|
||||
}
|
||||
|
||||
type LogRule struct {
|
||||
PathScope string
|
||||
OutputFile string
|
||||
|
@ -117,8 +34,8 @@ type LogRule struct {
|
|||
}
|
||||
|
||||
const (
|
||||
defaultLogFilename = "access.log"
|
||||
commonLogFormat = `{remote} ` + middleware.EmptyStringReplacer + ` [{when}] "{method} {uri} {proto}" {status} {size}`
|
||||
combinedLogFormat = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
|
||||
defaultLogFormat = commonLogFormat
|
||||
DefaultLogFilename = "access.log"
|
||||
CommonLogFormat = `{remote} ` + middleware.EmptyStringReplacer + ` [{when}] "{method} {uri} {proto}" {status} {size}`
|
||||
CombinedLogFormat = CommonLogFormat + ` "{>Referer}" "{>User-Agent}"`
|
||||
DefaultLogFormat = CommonLogFormat
|
||||
)
|
||||
|
|
|
@ -9,18 +9,6 @@ import (
|
|||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// New instantiates a new Redirect middleware.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
rules, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return Redirect{Next: next, Rules: rules}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Redirect is middleware to respond with HTTP redirects
|
||||
type Redirect struct {
|
||||
Next middleware.Handler
|
||||
|
@ -43,65 +31,8 @@ func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
|||
return rd.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) ([]Rule, error) {
|
||||
var redirects []Rule
|
||||
|
||||
for c.Next() {
|
||||
var rule 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
|
||||
}
|
||||
|
||||
// Rule describes an HTTP redirect rule.
|
||||
type Rule struct {
|
||||
From, To string
|
||||
Code int
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
|
|
@ -8,22 +8,10 @@ import (
|
|||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// New instantiates a new Rewrites middleware.
|
||||
func New(c middleware.Controller) (middleware.Middleware, error) {
|
||||
rewrites, err := parse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return Rewrite{Next: next, Rules: rewrites}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Rewrite is middleware to rewrite request locations internally before being handled.
|
||||
type Rewrite struct {
|
||||
Next middleware.Handler
|
||||
Rules []RewriteRule
|
||||
Rules []Rule
|
||||
}
|
||||
|
||||
// ServeHTTP implements the middleware.Handler interface.
|
||||
|
@ -37,29 +25,7 @@ func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
|
|||
return rw.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func parse(c middleware.Controller) ([]RewriteRule, error) {
|
||||
var rewrites []RewriteRule
|
||||
|
||||
for c.Next() {
|
||||
var rule RewriteRule
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// RewriteRule describes an internal location rewrite rule.
|
||||
type RewriteRule struct {
|
||||
// A Rule describes an internal location rewrite rule.
|
||||
type Rule struct {
|
||||
From, To string
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"os/signal"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
"github.com/mholt/caddy/config"
|
||||
)
|
||||
|
||||
// Server represents an instance of a server, which serves
|
||||
|
@ -28,7 +27,7 @@ type Server struct {
|
|||
// New creates a new Server which will bind to addr and serve
|
||||
// the sites/hosts configured in configs. This function does
|
||||
// not start serving.
|
||||
func New(addr string, configs []config.Config, tls bool) (*Server, error) {
|
||||
func New(addr string, configs []Config, tls bool) (*Server, error) {
|
||||
s := &Server{
|
||||
address: addr,
|
||||
tls: tls,
|
||||
|
@ -93,7 +92,7 @@ func (s *Server) Serve() error {
|
|||
}
|
||||
|
||||
if s.tls {
|
||||
var tlsConfigs []config.TLSConfig
|
||||
var tlsConfigs []TLSConfig
|
||||
for _, vh := range s.vhosts {
|
||||
tlsConfigs = append(tlsConfigs, vh.config.TLS)
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ func (s *Server) Serve() error {
|
|||
// multiple sites (different hostnames) to be served from the same address. This method is
|
||||
// adapted directly from the std lib's net/http ListenAndServeTLS function, which was
|
||||
// written by the Go Authors. It has been modified to support multiple certificate/key pairs.
|
||||
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []config.TLSConfig) error {
|
||||
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":https"
|
||||
|
|
|
@ -3,7 +3,6 @@ package server
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mholt/caddy/config"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
|
@ -12,7 +11,7 @@ import (
|
|||
// multiple sites on a single address, and this is what a
|
||||
// virtualHost allows us to do.
|
||||
type virtualHost struct {
|
||||
config config.Config
|
||||
config Config
|
||||
fileServer middleware.Handler
|
||||
stack middleware.Handler
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user