Commit 974acbf3 authored by Matthew Holt's avatar Matthew Holt

Partial support for location contexts in config files

parent 634b8b70
...@@ -63,7 +63,7 @@ type Config struct { ...@@ -63,7 +63,7 @@ type Config struct {
Port string Port string
Root string Root string
TLS TLSConfig TLS TLSConfig
Middleware []middleware.Middleware Middleware map[string][]middleware.Middleware
Startup []func() error Startup []func() error
MaxCPU int MaxCPU int
} }
......
package config package config
import "github.com/mholt/caddy/middleware"
// controller is a dispenser of tokens and also // controller is a dispenser of tokens and also
// facilitates setup with the server by providing // facilitates setup with the server by providing
// access to its configuration. It implements // access to its configuration. It implements
// the middleware.Controller interface. // the middleware.Controller interface.
type controller struct { type controller struct {
dispenser dispenser
parser *parser parser *parser
pathScope string
} }
// newController returns a new controller. // newController returns a new controller.
...@@ -43,3 +46,7 @@ func (c *controller) Host() string { ...@@ -43,3 +46,7 @@ func (c *controller) Host() string {
func (c *controller) Port() string { func (c *controller) Port() string {
return c.parser.cfg.Port return c.parser.cfg.Port
} }
func (c *controller) Context() middleware.Path {
return middleware.Path(c.pathScope)
}
...@@ -12,7 +12,9 @@ import ( ...@@ -12,7 +12,9 @@ import (
type dirFunc func(*parser) error type dirFunc func(*parser) error
// validDirectives is a map of valid, built-in directive names // validDirectives is a map of valid, built-in directive names
// to their parsing function. // 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 var validDirectives map[string]dirFunc
func init() { func init() {
......
...@@ -5,16 +5,30 @@ import ( ...@@ -5,16 +5,30 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/mholt/caddy/middleware"
) )
// parser is a type which can parse config files. type (
type parser struct { // parser is a type which can parse config files.
filename string // the name of the file that we're parsing parser struct {
lexer lexer // the lexer that is giving us tokens from the raw input filename string // the name of the file that we're parsing
cfg Config // each server gets one Config; this is the one we're currently building lexer lexer // the lexer that is giving us tokens from the raw input
other map[string]*controller // tokens to be parsed later by others (middleware generators) cfg Config // each server gets one Config; this is the one we're currently building
unused bool // sometimes the token won't be immediately consumed other []locationContext // tokens to be 'parsed' later by middleware generators
} scope *locationContext // the current location context (path scope) being populated
unused bool // sometimes a token will be read but not immediately consumed
}
// 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
}
)
// newParser makes a new parser and prepares it for parsing, given // newParser makes a new parser and prepares it for parsing, given
// the input to parse. // the input to parse.
...@@ -78,13 +92,14 @@ func (p *parser) next() bool { ...@@ -78,13 +92,14 @@ func (p *parser) next() bool {
// file for a single Config object (each server or // file for a single Config object (each server or
// virtualhost instance gets their own Config struct), // virtualhost instance gets their own Config struct),
// which is until the next address/server block. // which is until the next address/server block.
// Call this only after you know that the lexer has another // Call this only when you know that the lexer has another
// another token and you're not in the middle of a server // another token and you're not in another server
// block already. // block already.
func (p *parser) parseOne() error { func (p *parser) parseOne() error {
p.cfg = Config{} p.cfg = Config{
Middleware: make(map[string][]middleware.Middleware),
p.other = make(map[string]*controller) }
p.other = []locationContext{}
err := p.begin() err := p.begin()
if err != nil { if err != nil {
...@@ -102,19 +117,24 @@ func (p *parser) parseOne() error { ...@@ -102,19 +117,24 @@ func (p *parser) parseOne() error {
// unwrap gets the middleware generators from the middleware // unwrap gets the middleware generators from the middleware
// package in the order in which they are registered, and // package in the order in which they are registered, and
// executes the top-level functions (the generator function) // executes the top-level functions (the generator function)
// to expose the second layers which is the actual middleware. // to expose the second layers which are the actual middleware.
// This function should be called only after p has filled out // 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 that the entire server block has been consumed.
func (p *parser) unwrap() error { func (p *parser) unwrap() error {
for _, directive := range registry.ordered { for _, directive := range registry.ordered {
if disp, ok := p.other[directive]; ok { // 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...
if disp, ok := p.other[0].directives[directive]; ok {
if generator, ok := registry.directiveMap[directive]; ok { if generator, ok := registry.directiveMap[directive]; ok {
mid, err := generator(disp) mid, err := generator(disp)
if err != nil { if err != nil {
return err return err
} }
if mid != nil { if mid != nil {
p.cfg.Middleware = append(p.cfg.Middleware, mid) // 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 { } else {
return errors.New("No middleware bound to directive '" + directive + "'") return errors.New("No middleware bound to directive '" + directive + "'")
......
package config package config
import "errors"
// This file contains the recursive-descent parsing // This file contains the recursive-descent parsing
// functions. // functions.
...@@ -47,6 +49,14 @@ func (p *parser) addressBlock() error { ...@@ -47,6 +49,14 @@ func (p *parser) addressBlock() error {
return p.directives() return p.directives()
} }
// 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]
err = p.directives() err = p.directives()
if err != nil { if err != nil {
return err return err
...@@ -91,40 +101,66 @@ func (p *parser) directives() error { ...@@ -91,40 +101,66 @@ func (p *parser) directives() error {
// end of address scope // end of address scope
break break
} }
if p.tkn()[0] == '/' { if p.tkn()[0] == '/' || p.tkn()[0] == '*' {
// Path scope (a.k.a. location context) // Path scope (a.k.a. location context)
// Starts with / ('starts with') or * ('ends with').
// TODO: The parser can handle the syntax (obviously), but the // TODO: The parser can handle the syntax (obviously), but the
// implementation is incomplete. This is intentional, // implementation is incomplete. This is intentional,
// until we can better decide what kind of feature set we // until we can better decide what kind of feature set we
// want to support. Until this is ready, we leave this // want to support and how exactly we want these location
// syntax undocumented. // 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
}
}
// location := p.tkn() // 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() { if !p.next() {
return p.eofErr() return p.eofErr()
} }
err := p.openCurlyBrace() err := p.openCurlyBrace()
if err != nil { if err != nil {
return err 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() { for p.next() {
err := p.closeCurlyBrace() err := p.closeCurlyBrace()
if err == nil { // end of location context if err == nil {
break break
} }
// TODO: How should we give the context to the directives?
// Or how do we tell the server that these directives should only
// be executed for requests routed to the current path?
err = p.directive() err = p.directive()
if err != nil { if err != nil {
return err 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 { } else if err := p.directive(); err != nil {
return err return err
} }
...@@ -134,10 +170,11 @@ func (p *parser) directives() error { ...@@ -134,10 +170,11 @@ func (p *parser) directives() error {
// directive asserts that the current token is either a built-in // directive asserts that the current token is either a built-in
// directive or a registered middleware directive; otherwise an error // directive or a registered middleware directive; otherwise an error
// will be returned. // will be returned. If it is a valid directive, tokens will be
// collected.
func (p *parser) directive() error { func (p *parser) directive() error {
if fn, ok := validDirectives[p.tkn()]; ok { if fn, ok := validDirectives[p.tkn()]; ok {
// Built-in (standard) directive // Built-in (standard, or 'core') directive
err := fn(p) err := fn(p)
if err != nil { if err != nil {
return err return err
...@@ -159,6 +196,10 @@ func (p *parser) directive() error { ...@@ -159,6 +196,10 @@ func (p *parser) directive() error {
// It creates a controller which is stored in the parser for // It creates a controller which is stored in the parser for
// later use by the middleware. // later use by the middleware.
func (p *parser) collectTokens() error { func (p *parser) collectTokens() error {
if p.scope == nil {
return errors.New("Current scope cannot be nil")
}
directive := p.tkn() directive := p.tkn()
line := p.line() line := p.line()
nesting := 0 nesting := 0
...@@ -169,7 +210,7 @@ func (p *parser) collectTokens() error { ...@@ -169,7 +210,7 @@ func (p *parser) collectTokens() error {
// (the parsing logic in the middleware generator must // (the parsing logic in the middleware generator must
// account for multiple occurrences of its directive, even // account for multiple occurrences of its directive, even
// if that means returning an error or overwriting settings) // if that means returning an error or overwriting settings)
if existing, ok := p.other[directive]; ok { if existing, ok := p.scope.directives[directive]; ok {
cont = existing cont = existing
} }
...@@ -195,6 +236,6 @@ func (p *parser) collectTokens() error { ...@@ -195,6 +236,6 @@ func (p *parser) collectTokens() error {
return p.eofErr() return p.eofErr()
} }
p.other[directive] = cont p.scope.directives[directive] = cont
return nil return nil
} }
...@@ -29,6 +29,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) { ...@@ -29,6 +29,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) {
} }
for c.NextBlock() { for c.NextBlock() {
// A block of headers was opened...
h := Header{Name: c.Val()} h := Header{Name: c.Val()}
if c.NextArg() { if c.NextArg() {
...@@ -38,6 +40,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) { ...@@ -38,6 +40,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) {
head.Headers = append(head.Headers, h) head.Headers = append(head.Headers, h)
} }
if c.NextArg() { if c.NextArg() {
// ... or single header was defined as an argument instead.
h := Header{Name: c.Val()} h := Header{Name: c.Val()}
h.Value = c.Val() h.Value = c.Val()
......
...@@ -29,5 +29,6 @@ type ( ...@@ -29,5 +29,6 @@ type (
Root() string Root() string
Host() string Host() string
Port() string Port() string
Context() Path
} }
) )
...@@ -101,7 +101,11 @@ func (s *Server) buildStack() error { ...@@ -101,7 +101,11 @@ func (s *Server) buildStack() error {
} }
} }
s.compile(s.config.Middleware) // TODO: We only compile middleware for the "/" scope.
// Partial support for multiple location contexts already
// exists in the parser and config levels, but until full
// support is implemented, this is all we do right here.
s.compile(s.config.Middleware["/"])
return nil return nil
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment