diff --git a/config/controller.go b/config/controller.go index 20a289940..d0c9f5e58 100644 --- a/config/controller.go +++ b/config/controller.go @@ -47,6 +47,7 @@ func (c *controller) Port() string { return c.parser.cfg.Port } +// 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/dispenser.go b/config/dispenser.go index a1047f7e9..597c14234 100644 --- a/config/dispenser.go +++ b/config/dispenser.go @@ -5,10 +5,11 @@ import ( "fmt" ) -// dispenser is a type that gets exposed to middleware -// generators so that they can parse tokens to configure -// their instance. It basically dispenses tokens but can -// do so in a structured manner. +// 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 @@ -18,15 +19,13 @@ type dispenser struct { // Next loads the next token. Returns true if a token // was loaded; false otherwise. If false, all tokens -// have been consumed. -// TODO: Have the other Next functions call this one...? +// have already been consumed. func (d *dispenser) Next() bool { - if d.cursor >= len(d.tokens)-1 { - return false - } else { + if d.cursor < len(d.tokens)-1 { d.cursor++ return true } + return false } // NextArg loads the next token if it is on the same @@ -49,8 +48,10 @@ func (d *dispenser) NextArg() bool { return false } -// TODO: Assert that there's a line break and only advance -// the token if that's the case? (store an error otherwise) +// 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++ @@ -96,7 +97,8 @@ func (d *dispenser) NextBlock() bool { return true } -// Val gets the text of the current token. +// 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 "" @@ -105,23 +107,6 @@ func (d *dispenser) Val() string { } } -// 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("Unexpected line break after '" + d.Val() + "' (missing arguments?)") -} - -// 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) -} - // 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 @@ -157,3 +142,20 @@ func (d *dispenser) RemainingArgs() []string { 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("Unexpected line break after '" + d.Val() + "' (missing arguments?)") +} + +// 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/parser.go b/config/parser.go index 52b43a058..922c3f4d8 100644 --- a/config/parser.go +++ b/config/parser.go @@ -119,13 +119,18 @@ func (p *parser) parseOne() error { // 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 that the entire server block has been consumed. +// 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 ("/") - // 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... + // 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) @@ -141,6 +146,7 @@ func (p *parser) unwrap() error { } } } + return nil } diff --git a/middleware/gzip/gzip.go b/middleware/gzip/gzip.go index c72622620..111b5e3ea 100644 --- a/middleware/gzip/gzip.go +++ b/middleware/gzip/gzip.go @@ -11,6 +11,11 @@ import ( "github.com/mholt/caddy/middleware" ) +// Gzip is a http.Handler middleware type which gzips HTTP responses. +type Gzip struct { + Next http.HandlerFunc +} + // New creates a new gzip middleware instance. func New(c middleware.Controller) (middleware.Middleware, error) { return func(next http.HandlerFunc) http.HandlerFunc { @@ -19,12 +24,6 @@ func New(c middleware.Controller) (middleware.Middleware, error) { }, nil } -// Gzip is a http.Handler middleware type which gzips HTTP responses. -type Gzip struct { - Next http.HandlerFunc - // TODO: Compression level, other settings -} - // ServeHTTP serves a gzipped response if the client supports it. func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { diff --git a/middleware/middleware.go b/middleware/middleware.go index 72d2208d0..3ae3256bf 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -13,23 +13,76 @@ type ( // and returns the inner layer, which is the actual HandlerFunc. Middleware func(http.HandlerFunc) http.HandlerFunc - // Controller is the type which middleware generators use to access - // tokens and the server and any other information they need to - // configure themselves. + // A Control provides structured access to tokens from a configuration file + // and also to properties of the server being configured. Middleware generators + // use a Controller to construct their middleware instance. Controller interface { + // Next loads the next token. Returns true if a token + // was loaded; false otherwise. If false, all tokens + // have already been consumed. Next() bool + + // 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. NextArg() bool + + // 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. NextLine() bool + + // NextBlock advances the cursor to the next token only + // if the current token is an open curly brace on the + // same line. If so, that token is consumed and this + // function will return true until the closing curly + // brace gets consumed by this method. Usually, you would + // use this as the condition of a for loop to parse + // tokens while being inside a block. NextBlock() bool + + // Val gets the text of the current token. Val() string + + // Args is a convenience function that loads the next arguments + // (tokens on the same line) into an arbitrary number of strings + // pointed to in arguments. 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. Args(...*string) bool + + // RemainingArgs is a convenience function that loads any more arguments + // (tokens on the same line) into a slice and returns them. If an open curly + // brace token is encountered before the end of the line, that token is + // considered the end of the arguments (and the curly brace is not consumed). RemainingArgs() []string + + // ArgErr returns an argument error, meaning that another + // argument was expected but not found. In other words, + // a line break, EOF, or open curly brace was encountered instead of + // an argument. ArgErr() error + + // Err generates a custom parse error with a message of msg. Err(string) error + + // Startup registers a function to execute when the server starts. Startup(func() error) + + // Root returns the file path from which the server is serving. Root() string + + // Host returns the hostname the server is bound to. Host() string + + // Port returns the port that the server is listening on. Port() string + + // Context returns the path scope that the Controller is in. + // Note: This is not currently used, but may be in the future. Context() Path } ) diff --git a/server/server.go b/server/server.go index 1fd3fcc21..ebe42cf24 100644 --- a/server/server.go +++ b/server/server.go @@ -71,7 +71,6 @@ func (s *Server) Serve() error { server := &http.Server{ Addr: s.config.Address(), Handler: s, - // TODO: Make more of the server configurable, also more http2 configurability } http2.ConfigureServer(server, nil) // TODO: This may not be necessary after HTTP/2 merged into std lib