caddy/plugins.go
2016-08-17 17:02:01 -06:00

310 lines
10 KiB
Go

package caddy
import (
"fmt"
"net"
"sort"
"github.com/mholt/caddy/caddyfile"
)
// These are all the registered plugins.
var (
// serverTypes is a map of registered server types.
serverTypes = make(map[string]ServerType)
// plugins is a map of server type to map of plugin name to
// Plugin. These are the "general" plugins that may or may
// not be associated with a specific server type. If it's
// applicable to multiple server types or the server type is
// irrelevant, the key is empty string (""). But all plugins
// must have a name.
plugins = make(map[string]map[string]Plugin)
// parsingCallbacks maps server type to map of directive
// to list of callback functions. These aren't really
// plugins on their own, but are often registered from
// plugins.
parsingCallbacks = make(map[string]map[string][]ParsingCallback)
// caddyfileLoaders is the list of all Caddyfile loaders
// in registration order.
caddyfileLoaders []caddyfileLoader
)
// DescribePlugins returns a string describing the registered plugins.
func DescribePlugins() string {
str := "Server types:\n"
for name := range serverTypes {
str += " " + name + "\n"
}
// List the loaders in registration order
str += "\nCaddyfile loaders:\n"
for _, loader := range caddyfileLoaders {
str += " " + loader.name + "\n"
}
if defaultCaddyfileLoader.name != "" {
str += " " + defaultCaddyfileLoader.name + "\n"
}
// Let's alphabetize the rest of these...
var others []string
for stype, stypePlugins := range plugins {
for name := range stypePlugins {
var s string
if stype != "" {
s = stype + "."
}
s += name
others = append(others, s)
}
}
sort.Strings(others)
str += "\nOther plugins:\n"
for _, name := range others {
str += " " + name + "\n"
}
return str
}
// ValidDirectives returns the list of all directives that are
// recognized for the server type serverType. However, not all
// directives may be installed. This makes it possible to give
// more helpful error messages, like "did you mean ..." or
// "maybe you need to plug in ...".
func ValidDirectives(serverType string) []string {
stype, err := getServerType(serverType)
if err != nil {
return nil
}
return stype.Directives
}
// serverListener pairs a server to its listener and/or packetconn.
type serverListener struct {
server Server
listener net.Listener
packet net.PacketConn
}
// Context is a type which carries a server type through
// the load and setup phase; it maintains the state
// between loading the Caddyfile, then executing its
// directives, then making the servers for Caddy to
// manage. Typically, such state involves configuration
// structs, etc.
type Context interface {
// Called after the Caddyfile is parsed into server
// blocks but before the directives are executed,
// this method gives you an opportunity to inspect
// the server blocks and prepare for the execution
// of directives. Return the server blocks (which
// you may modify, if desired) and an error, if any.
// The first argument is the name or path to the
// configuration file (Caddyfile).
//
// This function can be a no-op and simply return its
// input if there is nothing to do here.
InspectServerBlocks(string, []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error)
// This is what Caddy calls to make server instances.
// By this time, all directives have been executed and,
// presumably, the context has enough state to produce
// server instances for Caddy to start.
MakeServers() ([]Server, error)
}
// RegisterServerType registers a server type srv by its
// name, typeName.
func RegisterServerType(typeName string, srv ServerType) {
if _, ok := serverTypes[typeName]; ok {
panic("server type already registered")
}
serverTypes[typeName] = srv
}
// ServerType contains information about a server type.
type ServerType struct {
// List of directives, in execution order, that are
// valid for this server type. Directives should be
// one word if possible and lower-cased.
Directives []string
// DefaultInput returns a default config input if none
// is otherwise loaded. This is optional, but highly
// recommended, otherwise a blank Caddyfile will be
// used.
DefaultInput func() Input
// The function that produces a new server type context.
// This will be called when a new Caddyfile is being
// loaded, parsed, and executed independently of any
// startup phases before this one. It's a way to keep
// each set of server instances separate and to reduce
// the amount of global state you need.
NewContext func() Context
}
// Plugin is a type which holds information about a plugin.
type Plugin struct {
// ServerType is the type of server this plugin is for.
// Can be empty if not applicable, or if the plugin
// can associate with any server type.
ServerType string
// Action is the plugin's setup function, if associated
// with a directive in the Caddyfile.
Action SetupFunc
}
// RegisterPlugin plugs in plugin. All plugins should register
// themselves, even if they do not perform an action associated
// with a directive. It is important for the process to know
// which plugins are available.
//
// The plugin MUST have a name: lower case and one word.
// If this plugin has an action, it must be the name of
// the directive that invokes it. A name is always required
// and must be unique for the server type.
func RegisterPlugin(name string, plugin Plugin) {
if name == "" {
panic("plugin must have a name")
}
if _, ok := plugins[plugin.ServerType]; !ok {
plugins[plugin.ServerType] = make(map[string]Plugin)
}
if _, dup := plugins[plugin.ServerType][name]; dup {
panic("plugin named " + name + " already registered for server type " + plugin.ServerType)
}
plugins[plugin.ServerType][name] = plugin
}
// ParsingCallback is a function that is called after
// a directive's setup functions have been executed
// for all the server blocks.
type ParsingCallback func(Context) error
// RegisterParsingCallback registers callback to be called after
// executing the directive afterDir for server type serverType.
func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) {
if _, ok := parsingCallbacks[serverType]; !ok {
parsingCallbacks[serverType] = make(map[string][]ParsingCallback)
}
parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback)
}
// SetupFunc is used to set up a plugin, or in other words,
// execute a directive. It will be called once per key for
// each server block it appears in.
type SetupFunc func(c *Controller) error
// DirectiveAction gets the action for directive dir of
// server type serverType.
func DirectiveAction(serverType, dir string) (SetupFunc, error) {
if stypePlugins, ok := plugins[serverType]; ok {
if plugin, ok := stypePlugins[dir]; ok {
return plugin.Action, nil
}
}
if genericPlugins, ok := plugins[""]; ok {
if plugin, ok := genericPlugins[dir]; ok {
return plugin.Action, nil
}
}
return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)",
dir, serverType)
}
// Loader is a type that can load a Caddyfile.
// It is passed the name of the server type.
// It returns an error only if something went
// wrong, not simply if there is no Caddyfile
// for this loader to load.
//
// A Loader should only load the Caddyfile if
// a certain condition or requirement is met,
// as returning a non-nil Input value along with
// another Loader will result in an error.
// In other words, loading the Caddyfile must
// be deliberate & deterministic, not haphazard.
//
// The exception is the default Caddyfile loader,
// which will be called only if no other Caddyfile
// loaders return a non-nil Input. The default
// loader may always return an Input value.
type Loader interface {
Load(serverType string) (Input, error)
}
// LoaderFunc is a convenience type similar to http.HandlerFunc
// that allows you to use a plain function as a Load() method.
type LoaderFunc func(serverType string) (Input, error)
// Load loads a Caddyfile.
func (lf LoaderFunc) Load(serverType string) (Input, error) {
return lf(serverType)
}
// RegisterCaddyfileLoader registers loader named name.
func RegisterCaddyfileLoader(name string, loader Loader) {
caddyfileLoaders = append(caddyfileLoaders, caddyfileLoader{name: name, loader: loader})
}
// SetDefaultCaddyfileLoader registers loader by name
// as the default Caddyfile loader if no others produce
// a Caddyfile. If another Caddyfile loader has already
// been set as the default, this replaces it.
//
// Do not call RegisterCaddyfileLoader on the same
// loader; that would be redundant.
func SetDefaultCaddyfileLoader(name string, loader Loader) {
defaultCaddyfileLoader = caddyfileLoader{name: name, loader: loader}
}
// loadCaddyfileInput iterates the registered Caddyfile loaders
// and, if needed, calls the default loader, to load a Caddyfile.
// It is an error if any of the loaders return an error or if
// more than one loader returns a Caddyfile.
func loadCaddyfileInput(serverType string) (Input, error) {
var loadedBy string
var caddyfileToUse Input
for _, l := range caddyfileLoaders {
cdyfile, err := l.loader.Load(serverType)
if err != nil {
return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err)
}
if cdyfile != nil {
if caddyfileToUse != nil {
return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name)
}
loaderUsed = l
caddyfileToUse = cdyfile
loadedBy = l.name
}
}
if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil {
cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType)
if err != nil {
return nil, err
}
if cdyfile != nil {
loaderUsed = defaultCaddyfileLoader
caddyfileToUse = cdyfile
}
}
return caddyfileToUse, nil
}
// caddyfileLoader pairs the name of a loader to the loader.
type caddyfileLoader struct {
name string
loader Loader
}
var (
defaultCaddyfileLoader caddyfileLoader // the default loader if all else fail
loaderUsed caddyfileLoader // the loader that was used (relevant for reloads)
)