diff options
author | Matthew Holt <[email protected]> | 2015-05-04 06:53:54 -0600 |
---|---|---|
committer | Matthew Holt <[email protected]> | 2015-05-04 11:02:46 -0600 |
commit | 995edf05661c42399bb1469a657860e2ec8e7bf7 (patch) | |
tree | 94614623ff1496ef8c989ae29ea76d73a09e8a20 | |
parent | 5f32f9b1c8f6054afcaed9e34bc937170a672757 (diff) | |
download | caddy-995edf05661c42399bb1469a657860e2ec8e7bf7.tar.gz caddy-995edf05661c42399bb1469a657860e2ec8e7bf7.zip |
Bringing in latest from master; refactoring under way
-rw-r--r-- | config/config.go | 153 | ||||
-rw-r--r-- | config/controller.go | 48 | ||||
-rw-r--r-- | config/controller_test.go | 37 | ||||
-rw-r--r-- | config/directives.go | 164 | ||||
-rw-r--r-- | config/dispenser.go | 159 | ||||
-rw-r--r-- | config/dispenser_test.go | 310 | ||||
-rw-r--r-- | config/import_test.txt | 1 | ||||
-rw-r--r-- | config/lexer.go | 114 | ||||
-rw-r--r-- | config/lexer_test.go | 139 | ||||
-rw-r--r-- | config/middleware.go | 82 | ||||
-rw-r--r-- | config/parser.go | 208 | ||||
-rw-r--r-- | config/parser_test.go | 330 | ||||
-rw-r--r-- | config/parsing.go | 318 | ||||
-rw-r--r-- | config/parsing_test.go | 1 | ||||
-rw-r--r-- | main.go | 56 | ||||
-rw-r--r-- | middleware/basicauth/basicauth.go | 65 | ||||
-rw-r--r-- | middleware/errors/errors.go | 96 | ||||
-rw-r--r-- | middleware/gzip/gzip.go | 7 | ||||
-rw-r--r-- | middleware/headers/headers.go | 6 | ||||
-rw-r--r-- | middleware/headers/new.go | 16 | ||||
-rw-r--r-- | middleware/headers/parse.go | 69 | ||||
-rw-r--r-- | middleware/log/log.go | 97 | ||||
-rw-r--r-- | middleware/redirect/redirect.go | 69 | ||||
-rw-r--r-- | middleware/rewrite/rewrite.go | 40 | ||||
-rw-r--r-- | server/server.go | 7 | ||||
-rw-r--r-- | server/virtualhost.go | 3 |
26 files changed, 169 insertions, 2426 deletions
diff --git a/config/config.go b/config/config.go index f069f4f05..bde7854eb 100644 --- a/config/config.go +++ b/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 - } - - for i := 0; i < len(cfgs); i++ { - cfgs[i].ConfigFile = filename + // 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), + } + + // 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) +// 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 -// 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, - }, +// 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 } diff --git a/config/controller.go b/config/controller.go deleted file mode 100644 index 1ce6db7f5..000000000 --- a/config/controller.go +++ /dev/null @@ -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) -} diff --git a/config/controller_test.go b/config/controller_test.go deleted file mode 100644 index 7dccf0f14..000000000 --- a/config/controller_test.go +++ /dev/null @@ -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) - } -} diff --git a/config/directives.go b/config/directives.go index e39eae235..76f29af73 100644 --- a/config/directives.go +++ b/config/directives.go @@ -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()) - } + // 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{}{} + } +} - 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 - } +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}, +} - p.cfg.Shutdown = append(p.cfg.Shutdown, shutdownfn) - return nil - }, - } +type directive struct { + name string + setup setupFunc } + +type setupFunc func(c *setup.Controller) (middleware.Middleware, error) diff --git a/config/dispenser.go b/config/dispenser.go deleted file mode 100644 index 13cd85425..000000000 --- a/config/dispenser.go +++ /dev/null @@ -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) -} diff --git a/config/dispenser_test.go b/config/dispenser_test.go deleted file mode 100644 index 2940feb1c..000000000 --- a/config/dispenser_test.go +++ /dev/null @@ -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 -} diff --git a/config/import_test.txt b/config/import_test.txt deleted file mode 100644 index 78cb08282..000000000 --- a/config/import_test.txt +++ /dev/null @@ -1 +0,0 @@ -root /test/imported/public_html
\ No newline at end of file diff --git a/config/lexer.go b/config/lexer.go deleted file mode 100644 index 96c883cff..000000000 --- a/config/lexer.go +++ /dev/null @@ -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) - } -} diff --git a/config/lexer_test.go b/config/lexer_test.go deleted file mode 100644 index 5b1cd2c25..000000000 --- a/config/lexer_test.go +++ /dev/null @@ -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 - } - } -} diff --git a/config/middleware.go b/config/middleware.go deleted file mode 100644 index 41af1826d..000000000 --- a/config/middleware.go +++ /dev/null @@ -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 -} diff --git a/config/parser.go b/config/parser.go deleted file mode 100644 index 1c8cc2c87..000000000 --- a/config/parser.go +++ /dev/null @@ -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) -} diff --git a/config/parser_test.go b/config/parser_test.go deleted file mode 100644 index 7502c317e..000000000 --- a/config/parser_test.go +++ /dev/null @@ -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) - } -} diff --git a/config/parsing.go b/config/parsing.go deleted file mode 100644 index 0da600b30..000000000 --- a/config/parsing.go +++ /dev/null @@ -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 -} diff --git a/config/parsing_test.go b/config/parsing_test.go deleted file mode 100644 index d912156be..000000000 --- a/config/parsing_test.go +++ /dev/null @@ -1 +0,0 @@ -package config @@ -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 { diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index 16efb172d..de29b8d9a 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -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. diff --git a/middleware/errors/errors.go b/middleware/errors/errors.go index 392c31e8b..e1c03e644 100644 --- a/middleware/errors/errors.go +++ b/middleware/errors/errors.go @@ -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" diff --git a/middleware/gzip/gzip.go b/middleware/gzip/gzip.go index 8bbc1d146..64c885e5d 100644 --- a/middleware/gzip/gzip.go +++ b/middleware/gzip/gzip.go @@ -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") { diff --git a/middleware/headers/headers.go b/middleware/headers/headers.go index 1c82076c7..4c6f424fb 100644 --- a/middleware/headers/headers.go +++ b/middleware/headers/headers.go @@ -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 } diff --git a/middleware/headers/new.go b/middleware/headers/new.go deleted file mode 100644 index 28ddd582a..000000000 --- a/middleware/headers/new.go +++ /dev/null @@ -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 -} diff --git a/middleware/headers/parse.go b/middleware/headers/parse.go deleted file mode 100644 index cfdf14b41..000000000 --- a/middleware/headers/parse.go +++ /dev/null @@ -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 -} diff --git a/middleware/log/log.go b/middleware/log/log.go index ffbfa69e8..39d8ae038 100644 --- a/middleware/log/log.go +++ b/middleware/log/log.go @@ -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 ) diff --git a/middleware/redirect/redirect.go b/middleware/redirect/redirect.go index 7a21ed277..c480de5e3 100644 --- a/middleware/redirect/redirect.go +++ b/middleware/redirect/redirect.go @@ -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, -} diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 9ce9381e9..870438dc2 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -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 } diff --git a/server/server.go b/server/server.go index 40cf100d3..d6f20abc2 100644 --- a/server/server.go +++ b/server/server.go @@ -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" diff --git a/server/virtualhost.go b/server/virtualhost.go index eab59423e..d4fe9c85e 100644 --- a/server/virtualhost.go +++ b/server/virtualhost.go @@ -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 } |