mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-25 01:09:04 +01:00
Partial support for location contexts in config files
This commit is contained in:
parent
634b8b707f
commit
974acbf38c
8 changed files with 113 additions and 34 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
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
|
||||||
|
@ -7,6 +9,7 @@ package config
|
||||||
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 {
|
||||||
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 (
|
||||||
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 (
|
||||||
"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.
|
||||||
|
parser struct {
|
||||||
filename string // the name of the file that we're parsing
|
filename string // the name of the file that we're parsing
|
||||||
lexer lexer // the lexer that is giving us tokens from the raw input
|
lexer lexer // the lexer that is giving us tokens from the raw input
|
||||||
cfg Config // each server gets one Config; this is the one we're currently building
|
cfg Config // each server gets one Config; this is the one we're currently building
|
||||||
other map[string]*controller // tokens to be parsed later by others (middleware generators)
|
other []locationContext // tokens to be 'parsed' later by middleware generators
|
||||||
unused bool // sometimes the token won't be immediately consumed
|
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 {
|
||||||
// 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 {
|
||||||
// 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 + "'")
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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.
|
||||||
|
|
||||||
// location := p.tkn()
|
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() {
|
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 {
|
||||||
|
|
||||||
// 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 {
|
||||||
// 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 {
|
||||||
// (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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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 (
|
||||||
Root() string
|
Root() string
|
||||||
Host() string
|
Host() string
|
||||||
Port() string
|
Port() string
|
||||||
|
Context() Path
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue