mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-23 19:46:56 +08:00
Partial support for location contexts in config files
This commit is contained in:
parent
634b8b707f
commit
974acbf38c
|
@ -63,7 +63,7 @@ type Config struct {
|
|||
Port string
|
||||
Root string
|
||||
TLS TLSConfig
|
||||
Middleware []middleware.Middleware
|
||||
Middleware map[string][]middleware.Middleware
|
||||
Startup []func() error
|
||||
MaxCPU int
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
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
|
||||
parser *parser
|
||||
pathScope string
|
||||
}
|
||||
|
||||
// newController returns a new controller.
|
||||
|
@ -43,3 +46,7 @@ func (c *controller) Host() string {
|
|||
func (c *controller) Port() string {
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -5,16 +5,30 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/caddy/middleware"
|
||||
)
|
||||
|
||||
// parser is a type which can parse config files.
|
||||
type 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
|
||||
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)
|
||||
unused bool // sometimes the token won't be immediately consumed
|
||||
}
|
||||
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
|
||||
cfg Config // each server gets one Config; this is the one we're currently building
|
||||
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
|
||||
// the input to parse.
|
||||
|
@ -78,13 +92,14 @@ func (p *parser) next() bool {
|
|||
// 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 after you know that the lexer has another
|
||||
// another token and you're not in the middle of a server
|
||||
// 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.cfg = Config{}
|
||||
|
||||
p.other = make(map[string]*controller)
|
||||
p.cfg = Config{
|
||||
Middleware: make(map[string][]middleware.Middleware),
|
||||
}
|
||||
p.other = []locationContext{}
|
||||
|
||||
err := p.begin()
|
||||
if err != nil {
|
||||
|
@ -102,19 +117,24 @@ func (p *parser) parseOne() error {
|
|||
// 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 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
|
||||
// p.other and that the entire server block has been consumed.
|
||||
func (p *parser) unwrap() error {
|
||||
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 {
|
||||
mid, err := generator(disp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
return errors.New("No middleware bound to directive '" + directive + "'")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package config
|
||||
|
||||
import "errors"
|
||||
|
||||
// This file contains the recursive-descent parsing
|
||||
// functions.
|
||||
|
||||
|
@ -47,6 +49,14 @@ func (p *parser) addressBlock() error {
|
|||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -91,40 +101,66 @@ func (p *parser) directives() error {
|
|||
// end of address scope
|
||||
break
|
||||
}
|
||||
if p.tkn()[0] == '/' {
|
||||
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. Until this is ready, we leave this
|
||||
// syntax undocumented.
|
||||
// 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.
|
||||
|
||||
// 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() {
|
||||
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 { // end of location context
|
||||
if err == nil {
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
@ -134,10 +170,11 @@ func (p *parser) directives() error {
|
|||
|
||||
// directive asserts that the current token is either a built-in
|
||||
// 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 {
|
||||
if fn, ok := validDirectives[p.tkn()]; ok {
|
||||
// Built-in (standard) directive
|
||||
// Built-in (standard, or 'core') directive
|
||||
err := fn(p)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -159,6 +196,10 @@ func (p *parser) directive() error {
|
|||
// 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
|
||||
|
@ -169,7 +210,7 @@ func (p *parser) collectTokens() error {
|
|||
// (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.other[directive]; ok {
|
||||
if existing, ok := p.scope.directives[directive]; ok {
|
||||
cont = existing
|
||||
}
|
||||
|
||||
|
@ -195,6 +236,6 @@ func (p *parser) collectTokens() error {
|
|||
return p.eofErr()
|
||||
}
|
||||
|
||||
p.other[directive] = cont
|
||||
p.scope.directives[directive] = cont
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) {
|
|||
}
|
||||
|
||||
for c.NextBlock() {
|
||||
// A block of headers was opened...
|
||||
|
||||
h := Header{Name: c.Val()}
|
||||
|
||||
if c.NextArg() {
|
||||
|
@ -38,6 +40,8 @@ func parse(c middleware.Controller) ([]HeaderRule, error) {
|
|||
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()
|
||||
|
|
|
@ -29,5 +29,6 @@ type (
|
|||
Root() string
|
||||
Host() 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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user