mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-22 11:33:06 +08:00
Architectural shift to using context for config and module state
This commit is contained in:
parent
ff5b4639d5
commit
1f0c061ce3
275
caddy.go
275
caddy.go
|
@ -1,127 +1,118 @@
|
||||||
package caddy2
|
package caddy2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/certmagic"
|
"github.com/mholt/certmagic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run runs Caddy with the given config.
|
// Run runs Caddy with the given config.
|
||||||
func Run(cfg *Config) error {
|
func Run(newCfg *Config) error {
|
||||||
// allow only one call to Start at a time,
|
|
||||||
// since various calls to LoadModule()
|
|
||||||
// access shared map moduleInstances
|
|
||||||
startMu.Lock()
|
|
||||||
defer startMu.Unlock()
|
|
||||||
|
|
||||||
// because we will need to roll back any state
|
|
||||||
// modifications if this function errors, we
|
|
||||||
// keep a single error value and scope all
|
|
||||||
// sub-operations to their own functions to
|
|
||||||
// ensure this error value does not get
|
|
||||||
// overridden or missed when it should have
|
|
||||||
// been set by a short assignment
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// prepare the new config for use
|
|
||||||
cfg.apps = make(map[string]App)
|
|
||||||
cfg.moduleStates = make(map[string]interface{})
|
|
||||||
|
|
||||||
// reset the shared moduleInstances map; but
|
|
||||||
// keep a temporary reference to the current
|
|
||||||
// one so we can transfer over any necessary
|
|
||||||
// state to the new modules or to roll back
|
|
||||||
// if necessary
|
|
||||||
oldModuleInstances := moduleInstances
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
moduleInstances = oldModuleInstances
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
moduleInstances = make(map[string][]interface{})
|
|
||||||
|
|
||||||
// set up storage and make it CertMagic's default storage, too
|
|
||||||
err = func() error {
|
|
||||||
if cfg.StorageRaw != nil {
|
|
||||||
val, err := LoadModuleInline("system", "caddy.storage", cfg.StorageRaw)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading storage module: %v", err)
|
|
||||||
}
|
|
||||||
stor, err := val.(StorageConverter).CertMagicStorage()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating storage value: %v", err)
|
|
||||||
}
|
|
||||||
cfg.storage = stor
|
|
||||||
cfg.StorageRaw = nil // allow GC to deallocate - TODO: Does this help?
|
|
||||||
}
|
|
||||||
if cfg.storage == nil {
|
|
||||||
cfg.storage = &certmagic.FileStorage{Path: dataDir()}
|
|
||||||
}
|
|
||||||
certmagic.Default.Storage = cfg.storage
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load, Provision, Validate
|
|
||||||
err = func() error {
|
|
||||||
for modName, rawMsg := range cfg.AppsRaw {
|
|
||||||
val, err := LoadModule(modName, rawMsg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading app module '%s': %v", modName, err)
|
|
||||||
}
|
|
||||||
cfg.apps[modName] = val.(App)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap old config with the new one, and
|
|
||||||
// roll back this change if anything fails
|
|
||||||
currentCfgMu.Lock()
|
currentCfgMu.Lock()
|
||||||
oldCfg := currentCfg
|
defer currentCfgMu.Unlock()
|
||||||
currentCfg = cfg
|
|
||||||
currentCfgMu.Unlock()
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
currentCfgMu.Lock()
|
|
||||||
currentCfg = oldCfg
|
|
||||||
currentCfgMu.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Start
|
if newCfg != nil {
|
||||||
err = func() error {
|
// because we will need to roll back any state
|
||||||
h := Handle{cfg}
|
// modifications if this function errors, we
|
||||||
for name, a := range cfg.apps {
|
// keep a single error value and scope all
|
||||||
err := a.Start(h)
|
// sub-operations to their own functions to
|
||||||
|
// ensure this error value does not get
|
||||||
|
// overridden or missed when it should have
|
||||||
|
// been set by a short assignment
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// prepare the new config for use
|
||||||
|
newCfg.apps = make(map[string]App)
|
||||||
|
|
||||||
|
// create a context within which to load
|
||||||
|
// modules - essentially our new config's
|
||||||
|
// execution environment; be sure that
|
||||||
|
// cleanup occurs when we return if there
|
||||||
|
// was an error; otherwise, it will get
|
||||||
|
// cleaned up on next config cycle
|
||||||
|
ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
|
||||||
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for otherAppName, otherApp := range cfg.apps {
|
cancel() // clean up now
|
||||||
err := otherApp.Stop()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("aborting app %s: %v", otherAppName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s app module: start: %v", name, err)
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
newCfg.cancelFunc = cancel // clean up later
|
||||||
|
|
||||||
|
// set up storage and make it CertMagic's default storage, too
|
||||||
|
err = func() error {
|
||||||
|
if newCfg.StorageRaw != nil {
|
||||||
|
val, err := ctx.LoadModuleInline("system", "caddy.storage", newCfg.StorageRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading storage module: %v", err)
|
||||||
|
}
|
||||||
|
stor, err := val.(StorageConverter).CertMagicStorage()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating storage value: %v", err)
|
||||||
|
}
|
||||||
|
newCfg.storage = stor
|
||||||
|
newCfg.StorageRaw = nil // allow GC to deallocate - TODO: Does this help?
|
||||||
|
}
|
||||||
|
if newCfg.storage == nil {
|
||||||
|
newCfg.storage = &certmagic.FileStorage{Path: dataDir()}
|
||||||
|
}
|
||||||
|
certmagic.Default.Storage = newCfg.storage
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load, Provision, Validate
|
||||||
|
err = func() error {
|
||||||
|
for modName, rawMsg := range newCfg.AppsRaw {
|
||||||
|
val, err := ctx.LoadModule(modName, rawMsg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading app module '%s': %v", modName, err)
|
||||||
|
}
|
||||||
|
newCfg.apps[modName] = val.(App)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start
|
||||||
|
err = func() error {
|
||||||
|
var started []string
|
||||||
|
for name, a := range newCfg.apps {
|
||||||
|
err := a.Start()
|
||||||
|
if err != nil {
|
||||||
|
for _, otherAppName := range started {
|
||||||
|
err2 := newCfg.apps[otherAppName].Stop()
|
||||||
|
if err2 != nil {
|
||||||
|
err = fmt.Errorf("%v; additionally, aborting app %s: %v",
|
||||||
|
err, otherAppName, err2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s app module: start: %v", name, err)
|
||||||
|
}
|
||||||
|
started = append(started, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop
|
// swap old config with the new one
|
||||||
|
oldCfg := currentCfg
|
||||||
|
currentCfg = newCfg
|
||||||
|
|
||||||
|
// Stop, Cleanup
|
||||||
if oldCfg != nil {
|
if oldCfg != nil {
|
||||||
for name, a := range oldCfg.apps {
|
for name, a := range oldCfg.apps {
|
||||||
err := a.Stop()
|
err := a.Stop()
|
||||||
|
@ -129,26 +120,9 @@ func Run(cfg *Config) error {
|
||||||
log.Printf("[ERROR] stop %s: %v", name, err)
|
log.Printf("[ERROR] stop %s: %v", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// shut down listeners that are no longer being used
|
// clean up old modules
|
||||||
err = func() error {
|
oldCfg.cancelFunc()
|
||||||
listenersMu.Lock()
|
|
||||||
for key, info := range listeners {
|
|
||||||
if atomic.LoadInt32(&info.usage) == 0 {
|
|
||||||
err := info.ln.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[ERROR] closing listener %s: %v", info.ln.Addr(), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(listeners, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listenersMu.Unlock()
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -156,7 +130,7 @@ func Run(cfg *Config) error {
|
||||||
|
|
||||||
// App is a thing that Caddy runs.
|
// App is a thing that Caddy runs.
|
||||||
type App interface {
|
type App interface {
|
||||||
Start(Handle) error
|
Start() error
|
||||||
Stop() error
|
Stop() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,46 +146,7 @@ type Config struct {
|
||||||
// keyed by module name.
|
// keyed by module name.
|
||||||
apps map[string]App
|
apps map[string]App
|
||||||
|
|
||||||
// moduleStates stores the optional "global" state
|
cancelFunc context.CancelFunc
|
||||||
// values of every module used by this configuration,
|
|
||||||
// keyed by module name.
|
|
||||||
moduleStates map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle allows app modules to access
|
|
||||||
// the top-level Config in a controlled
|
|
||||||
// manner without needing to rely on
|
|
||||||
// global state.
|
|
||||||
type Handle struct {
|
|
||||||
current *Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// App returns the configured app named name. If no app with
|
|
||||||
// that name is currently configured, a new empty one will be
|
|
||||||
// instantiated. (The app module must still be registered.)
|
|
||||||
func (h Handle) App(name string) (interface{}, error) {
|
|
||||||
if app, ok := h.current.apps[name]; ok {
|
|
||||||
return app, nil
|
|
||||||
}
|
|
||||||
modVal, err := LoadModule(name, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
|
|
||||||
}
|
|
||||||
h.current.apps[name] = modVal.(App)
|
|
||||||
return modVal, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStorage returns the configured Caddy storage implementation.
|
|
||||||
// If no storage implementation is explicitly configured, the
|
|
||||||
// default one is returned instead, as long as there is a current
|
|
||||||
// configuration loaded.
|
|
||||||
func GetStorage() certmagic.Storage {
|
|
||||||
currentCfgMu.RLock()
|
|
||||||
defer currentCfgMu.RUnlock()
|
|
||||||
if currentCfg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return currentCfg.storage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration is a JSON-string-unmarshable duration type.
|
// Duration is a JSON-string-unmarshable duration type.
|
||||||
|
@ -236,17 +171,3 @@ var (
|
||||||
currentCfg *Config
|
currentCfg *Config
|
||||||
currentCfgMu sync.RWMutex
|
currentCfgMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// moduleInstances stores the individual instantiated
|
|
||||||
// values of modules, keyed by module name. The list
|
|
||||||
// of instances of each module get passed into the
|
|
||||||
// respective module's OnLoad callback, so they can
|
|
||||||
// set up any global state and/or make sure their
|
|
||||||
// configuration, when viewed as a whole, is valid.
|
|
||||||
// Since this list is shared, only one Start() routine
|
|
||||||
// must be allowed to happen at any given time.
|
|
||||||
var moduleInstances = make(map[string][]interface{})
|
|
||||||
|
|
||||||
// startMu ensures that only one Start() happens at a time.
|
|
||||||
// This is important since moduleInstances is shared state.
|
|
||||||
var startMu sync.Mutex
|
|
||||||
|
|
126
context.go
Normal file
126
context.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
package caddy2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/mholt/certmagic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
context.Context
|
||||||
|
moduleInstances map[string][]interface{}
|
||||||
|
cfg *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(ctx Context) (Context, context.CancelFunc) {
|
||||||
|
newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg}
|
||||||
|
c, cancel := context.WithCancel(ctx.Context)
|
||||||
|
wrappedCancel := func() {
|
||||||
|
cancel()
|
||||||
|
for modName, modInstances := range newCtx.moduleInstances {
|
||||||
|
for _, inst := range modInstances {
|
||||||
|
if cu, ok := inst.(CleanerUpper); ok {
|
||||||
|
err := cu.Cleanup()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] %s (%p): cleanup: %v", modName, inst, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newCtx.Context = c
|
||||||
|
return newCtx, wrappedCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
|
||||||
|
modulesMu.Lock()
|
||||||
|
mod, ok := modules[name]
|
||||||
|
modulesMu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown module: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod.New == nil {
|
||||||
|
return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := mod.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("initializing module '%s': %v", mod.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// value must be a pointer for unmarshaling into concrete type
|
||||||
|
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
|
||||||
|
val = reflect.New(rv.Type()).Elem().Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in its config only if there is a config to fill in
|
||||||
|
if len(rawMsg) > 0 {
|
||||||
|
err = json.Unmarshal(rawMsg, &val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prov, ok := val.(Provisioner); ok {
|
||||||
|
err := prov.Provision(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if validator, ok := val.(Validator); ok {
|
||||||
|
err := validator.Validate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if cleanerUpper, ok := val.(CleanerUpper); ok {
|
||||||
|
err2 := cleanerUpper.Cleanup()
|
||||||
|
if err2 != nil {
|
||||||
|
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.moduleInstances[name] = append(ctx.moduleInstances[name], val)
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
|
||||||
|
moduleName, err := getModuleNameInline(moduleNameKey, raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := ctx.LoadModule(moduleScope+"."+moduleName, raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// App returns the configured app named name. If no app with
|
||||||
|
// that name is currently configured, a new empty one will be
|
||||||
|
// instantiated. (The app module must still be registered.)
|
||||||
|
func (ctx Context) App(name string) (interface{}, error) {
|
||||||
|
if app, ok := ctx.cfg.apps[name]; ok {
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
modVal, err := ctx.LoadModule(name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
|
||||||
|
}
|
||||||
|
ctx.cfg.apps[name] = modVal.(App)
|
||||||
|
return modVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage returns the configured Caddy storage implementation.
|
||||||
|
func (ctx Context) Storage() certmagic.Storage {
|
||||||
|
return ctx.cfg.storage
|
||||||
|
}
|
33
listeners.go
33
listeners.go
|
@ -9,6 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Listen returns a listener suitable for use in a Caddy module.
|
// Listen returns a listener suitable for use in a Caddy module.
|
||||||
|
// Always be sure to close listeners when you are done with them.
|
||||||
func Listen(network, addr string) (net.Listener, error) {
|
func Listen(network, addr string) (net.Listener, error) {
|
||||||
lnKey := network + "/" + addr
|
lnKey := network + "/" + addr
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ func Listen(network, addr string) (net.Listener, error) {
|
||||||
defer listenersMu.Unlock()
|
defer listenersMu.Unlock()
|
||||||
|
|
||||||
// if listener already exists, increment usage counter, then return listener
|
// if listener already exists, increment usage counter, then return listener
|
||||||
if lnInfo, ok := listeners[lnKey]; ok {
|
if lnUsage, ok := listeners[lnKey]; ok {
|
||||||
atomic.AddInt32(&lnInfo.usage, 1)
|
atomic.AddInt32(&lnUsage.usage, 1)
|
||||||
return &fakeCloseListener{usage: &lnInfo.usage, Listener: lnInfo.ln}, nil
|
return &fakeCloseListener{usage: &lnUsage.usage, key: lnKey, Listener: lnUsage.ln}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// or, create new one and save it
|
// or, create new one and save it
|
||||||
|
@ -28,10 +29,10 @@ func Listen(network, addr string) (net.Listener, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure to start its usage counter at 1
|
// make sure to start its usage counter at 1
|
||||||
lnInfo := &listenerUsage{usage: 1, ln: ln}
|
lnUsage := &listenerUsage{usage: 1, ln: ln}
|
||||||
listeners[lnKey] = lnInfo
|
listeners[lnKey] = lnUsage
|
||||||
|
|
||||||
return &fakeCloseListener{usage: &lnInfo.usage, Listener: ln}, nil
|
return &fakeCloseListener{usage: &lnUsage.usage, key: lnKey, Listener: ln}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeCloseListener's Close() method is a no-op. This allows
|
// fakeCloseListener's Close() method is a no-op. This allows
|
||||||
|
@ -42,6 +43,7 @@ func Listen(network, addr string) (net.Listener, error) {
|
||||||
type fakeCloseListener struct {
|
type fakeCloseListener struct {
|
||||||
closed int32 // accessed atomically
|
closed int32 // accessed atomically
|
||||||
usage *int32 // accessed atomically
|
usage *int32 // accessed atomically
|
||||||
|
key string
|
||||||
net.Listener
|
net.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +82,9 @@ func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close stops accepting new connections, but does not
|
// Close stops accepting new connections without
|
||||||
// actually close the underlying listener.
|
// closing the underlying listener, unless no one
|
||||||
|
// else is using it.
|
||||||
func (fcl *fakeCloseListener) Close() error {
|
func (fcl *fakeCloseListener) Close() error {
|
||||||
if atomic.CompareAndSwapInt32(&fcl.closed, 0, 1) {
|
if atomic.CompareAndSwapInt32(&fcl.closed, 0, 1) {
|
||||||
// unfortunately, there is no way to cancel any
|
// unfortunately, there is no way to cancel any
|
||||||
|
@ -99,8 +102,18 @@ func (fcl *fakeCloseListener) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// since we're no longer using this listener,
|
// since we're no longer using this listener,
|
||||||
// decrement the usage counter
|
// decrement the usage counter and, if no one
|
||||||
atomic.AddInt32(fcl.usage, -1)
|
// else is using it, close underlying listener
|
||||||
|
if atomic.AddInt32(fcl.usage, -1) == 0 {
|
||||||
|
listenersMu.Lock()
|
||||||
|
delete(listeners, fcl.key)
|
||||||
|
listenersMu.Unlock()
|
||||||
|
err := fcl.Listener.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
95
modules.go
95
modules.go
|
@ -3,7 +3,6 @@ package caddy2
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -32,9 +31,6 @@ func RegisterModule(mod Module) error {
|
||||||
if mod.Name == "caddy" {
|
if mod.Name == "caddy" {
|
||||||
return fmt.Errorf("modules cannot be named 'caddy'")
|
return fmt.Errorf("modules cannot be named 'caddy'")
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(mod.Name, "caddy.") {
|
|
||||||
return fmt.Errorf("modules cannot be namespaced in 'caddy'")
|
|
||||||
}
|
|
||||||
|
|
||||||
modulesMu.Lock()
|
modulesMu.Lock()
|
||||||
defer modulesMu.Unlock()
|
defer modulesMu.Unlock()
|
||||||
|
@ -123,88 +119,6 @@ func Modules() []string {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadModule decodes rawMsg into a new instance of mod and
|
|
||||||
// returns the value. If mod.New() does not return a pointer
|
|
||||||
// value, it is converted to one so that it is unmarshaled
|
|
||||||
// into the underlying concrete type. If mod.New is nil, an
|
|
||||||
// error is returned. If the module implements Validator or
|
|
||||||
// Provisioner interfaces, those methods are invoked to
|
|
||||||
// ensure the module is fully configured and valid before
|
|
||||||
// being used.
|
|
||||||
func LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
|
|
||||||
modulesMu.Lock()
|
|
||||||
mod, ok := modules[name]
|
|
||||||
modulesMu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unknown module: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mod.New == nil {
|
|
||||||
return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := mod.New()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("initializing module '%s': %v", mod.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// value must be a pointer for unmarshaling into concrete type
|
|
||||||
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
|
|
||||||
val = reflect.New(rv.Type()).Elem().Addr().Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill in its config only if there is a config to fill in
|
|
||||||
if len(rawMsg) > 0 {
|
|
||||||
err = json.Unmarshal(rawMsg, &val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if prov, ok := val.(Provisioner); ok {
|
|
||||||
err := prov.Provision()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if validator, ok := val.(Validator); ok {
|
|
||||||
err := validator.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleInstances[mod.Name] = append(moduleInstances[mod.Name], val)
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadModuleInline loads a module from a JSON raw message which decodes
|
|
||||||
// to a map[string]interface{}, where one of the keys is moduleNameKey
|
|
||||||
// and the corresponding value is the module name as a string, which
|
|
||||||
// can be found in the given scope.
|
|
||||||
//
|
|
||||||
// This allows modules to be decoded into their concrete types and
|
|
||||||
// used when their names cannot be the unique key in a map, such as
|
|
||||||
// when there are multiple instances in the map or it appears in an
|
|
||||||
// array (where there are no custom keys). In other words, the key
|
|
||||||
// containing the module name is treated special/separate from all
|
|
||||||
// the other keys.
|
|
||||||
func LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
|
|
||||||
moduleName, err := getModuleNameInline(moduleNameKey, raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := LoadModule(moduleScope+"."+moduleName, raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getModuleNameInline loads the string value from raw of moduleNameKey,
|
// getModuleNameInline loads the string value from raw of moduleNameKey,
|
||||||
// where raw must be a JSON encoding of a map.
|
// where raw must be a JSON encoding of a map.
|
||||||
func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, error) {
|
func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, error) {
|
||||||
|
@ -228,14 +142,19 @@ func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, err
|
||||||
// always be fast (imperceptible running time) and an error should
|
// always be fast (imperceptible running time) and an error should
|
||||||
// be returned only if the value's configuration is invalid.
|
// be returned only if the value's configuration is invalid.
|
||||||
type Validator interface {
|
type Validator interface {
|
||||||
Validate() error
|
Validate(Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provisioner is implemented by modules which may need to perform
|
// Provisioner is implemented by modules which may need to perform
|
||||||
// some additional "setup" steps immediately after being loaded.
|
// some additional "setup" steps immediately after being loaded.
|
||||||
// This method will be called after Validate() (if implemented).
|
// This method will be called after Validate() (if implemented).
|
||||||
type Provisioner interface {
|
type Provisioner interface {
|
||||||
Provision() error
|
Provision(Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: different name...
|
||||||
|
type CleanerUpper interface {
|
||||||
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -37,16 +37,20 @@ type App struct {
|
||||||
Servers map[string]*Server `json:"servers"`
|
Servers map[string]*Server `json:"servers"`
|
||||||
|
|
||||||
servers []*http.Server
|
servers []*http.Server
|
||||||
|
|
||||||
|
ctx caddy2.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision sets up the app.
|
// Provision sets up the app.
|
||||||
func (hc *App) Provision() error {
|
func (app *App) Provision(ctx caddy2.Context) error {
|
||||||
for _, srv := range hc.Servers {
|
app.ctx = ctx
|
||||||
err := srv.Routes.Provision()
|
|
||||||
|
for _, srv := range app.Servers {
|
||||||
|
err := srv.Routes.Provision(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up server routes: %v", err)
|
return fmt.Errorf("setting up server routes: %v", err)
|
||||||
}
|
}
|
||||||
err = srv.Errors.Routes.Provision()
|
err = srv.Errors.Routes.Provision(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up server error handling routes: %v", err)
|
return fmt.Errorf("setting up server error handling routes: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -56,10 +60,10 @@ func (hc *App) Provision() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ensures the app's configuration is valid.
|
// Validate ensures the app's configuration is valid.
|
||||||
func (hc *App) Validate() error {
|
func (app *App) Validate() error {
|
||||||
// each server must use distinct listener addresses
|
// each server must use distinct listener addresses
|
||||||
lnAddrs := make(map[string]string)
|
lnAddrs := make(map[string]string)
|
||||||
for srvName, srv := range hc.Servers {
|
for srvName, srv := range app.Servers {
|
||||||
for _, addr := range srv.Listen {
|
for _, addr := range srv.Listen {
|
||||||
netw, expanded, err := parseListenAddr(addr)
|
netw, expanded, err := parseListenAddr(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -78,13 +82,13 @@ func (hc *App) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start runs the app. It sets up automatic HTTPS if enabled.
|
// Start runs the app. It sets up automatic HTTPS if enabled.
|
||||||
func (hc *App) Start(handle caddy2.Handle) error {
|
func (app *App) Start() error {
|
||||||
err := hc.automaticHTTPS(handle)
|
err := app.automaticHTTPS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("enabling automatic HTTPS: %v", err)
|
return fmt.Errorf("enabling automatic HTTPS: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for srvName, srv := range hc.Servers {
|
for srvName, srv := range app.Servers {
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||||
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
|
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
|
||||||
|
@ -110,13 +114,13 @@ func (hc *App) Start(handle caddy2.Handle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable TLS
|
// enable TLS
|
||||||
httpPort := hc.HTTPPort
|
httpPort := app.HTTPPort
|
||||||
if httpPort == 0 {
|
if httpPort == 0 {
|
||||||
httpPort = DefaultHTTPPort
|
httpPort = DefaultHTTPPort
|
||||||
}
|
}
|
||||||
_, port, _ := net.SplitHostPort(addr)
|
_, port, _ := net.SplitHostPort(addr)
|
||||||
if len(srv.TLSConnPolicies) > 0 && port != strconv.Itoa(httpPort) {
|
if len(srv.TLSConnPolicies) > 0 && port != strconv.Itoa(httpPort) {
|
||||||
tlsCfg, err := srv.TLSConnPolicies.TLSConfig(handle)
|
tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err)
|
return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err)
|
||||||
}
|
}
|
||||||
|
@ -124,7 +128,7 @@ func (hc *App) Start(handle caddy2.Handle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.Serve(ln)
|
go s.Serve(ln)
|
||||||
hc.servers = append(hc.servers, s)
|
app.servers = append(app.servers, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,14 +137,14 @@ func (hc *App) Start(handle caddy2.Handle) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop gracefully shuts down the HTTP server.
|
// Stop gracefully shuts down the HTTP server.
|
||||||
func (hc *App) Stop() error {
|
func (app *App) Stop() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if hc.GracePeriod > 0 {
|
if app.GracePeriod > 0 {
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(hc.GracePeriod))
|
ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
for _, s := range hc.servers {
|
for _, s := range app.servers {
|
||||||
err := s.Shutdown(ctx)
|
err := s.Shutdown(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -149,8 +153,8 @@ func (hc *App) Stop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
func (app *App) automaticHTTPS() error {
|
||||||
tlsAppIface, err := handle.App("tls")
|
tlsAppIface, err := app.ctx.App("tls")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting tls app: %v", err)
|
return fmt.Errorf("getting tls app: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -159,7 +163,7 @@ func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||||
lnAddrMap := make(map[string]struct{})
|
lnAddrMap := make(map[string]struct{})
|
||||||
var redirRoutes RouteList
|
var redirRoutes RouteList
|
||||||
|
|
||||||
for srvName, srv := range hc.Servers {
|
for srvName, srv := range app.Servers {
|
||||||
srv.tlsApp = tlsApp
|
srv.tlsApp = tlsApp
|
||||||
|
|
||||||
if srv.DisableAutoHTTPS {
|
if srv.DisableAutoHTTPS {
|
||||||
|
@ -209,7 +213,7 @@ func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
|
return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
|
||||||
}
|
}
|
||||||
httpRedirLnAddr := joinListenAddr(netw, host, strconv.Itoa(hc.HTTPPort))
|
httpRedirLnAddr := joinListenAddr(netw, host, strconv.Itoa(app.HTTPPort))
|
||||||
lnAddrMap[httpRedirLnAddr] = struct{}{}
|
lnAddrMap[httpRedirLnAddr] = struct{}{}
|
||||||
|
|
||||||
if parts := strings.SplitN(port, "-", 2); len(parts) == 2 {
|
if parts := strings.SplitN(port, "-", 2); len(parts) == 2 {
|
||||||
|
@ -217,7 +221,7 @@ func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||||
}
|
}
|
||||||
redirTo := "https://{request.host}"
|
redirTo := "https://{request.host}"
|
||||||
|
|
||||||
httpsPort := hc.HTTPSPort
|
httpsPort := app.HTTPSPort
|
||||||
if httpsPort == 0 {
|
if httpsPort == 0 {
|
||||||
httpsPort = DefaultHTTPSPort
|
httpsPort = DefaultHTTPSPort
|
||||||
}
|
}
|
||||||
|
@ -253,13 +257,13 @@ func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
if hc.listenerTaken(netw, a) {
|
if app.listenerTaken(netw, a) {
|
||||||
continue mapLoop
|
continue mapLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lnAddrs = append(lnAddrs, addr)
|
lnAddrs = append(lnAddrs, addr)
|
||||||
}
|
}
|
||||||
hc.Servers["auto_https_redirects"] = &Server{
|
app.Servers["auto_https_redirects"] = &Server{
|
||||||
Listen: lnAddrs,
|
Listen: lnAddrs,
|
||||||
Routes: redirRoutes,
|
Routes: redirRoutes,
|
||||||
DisableAutoHTTPS: true,
|
DisableAutoHTTPS: true,
|
||||||
|
@ -269,8 +273,8 @@ func (hc *App) automaticHTTPS(handle caddy2.Handle) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hc *App) listenerTaken(network, address string) bool {
|
func (app *App) listenerTaken(network, address string) bool {
|
||||||
for _, srv := range hc.Servers {
|
for _, srv := range app.Servers {
|
||||||
for _, addr := range srv.Listen {
|
for _, addr := range srv.Listen {
|
||||||
netw, addrs, err := parseListenAddr(addr)
|
netw, addrs, err := parseListenAddr(addr)
|
||||||
if err != nil || netw != network {
|
if err != nil || netw != network {
|
||||||
|
@ -491,3 +495,4 @@ const (
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var _ HTTPInterfaces = middlewareResponseWriter{}
|
var _ HTTPInterfaces = middlewareResponseWriter{}
|
||||||
|
var _ caddy2.App = (*App)(nil)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package reverseproxy
|
package reverseproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"bitbucket.org/lightcodelabs/caddy2"
|
"bitbucket.org/lightcodelabs/caddy2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -11,36 +9,5 @@ func init() {
|
||||||
caddy2.RegisterModule(caddy2.Module{
|
caddy2.RegisterModule(caddy2.Module{
|
||||||
Name: "http.responders.reverse_proxy",
|
Name: "http.responders.reverse_proxy",
|
||||||
New: func() (interface{}, error) { return new(LoadBalanced), nil },
|
New: func() (interface{}, error) { return new(LoadBalanced), nil },
|
||||||
OnLoad: func(instances []interface{}, _ interface{}) (interface{}, error) {
|
|
||||||
// we don't need to do anything with prior state because healthcheckers are
|
|
||||||
// cleaned up in OnUnload.
|
|
||||||
s := &State{
|
|
||||||
HealthCheckers: []*HealthChecker{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range instances {
|
|
||||||
lb := i.(*LoadBalanced)
|
|
||||||
|
|
||||||
err := NewLoadBalancedReverseProxy(lb, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
},
|
|
||||||
OnUnload: func(state interface{}) error {
|
|
||||||
s, ok := state.(*State)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("proxy OnLoad: prior state not expected proxy.State type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup old healthcheckers
|
|
||||||
for _, hc := range s.HealthCheckers {
|
|
||||||
hc.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLoadBalancedReverseProxy returns a collection of Upstreams that are to be loadbalanced.
|
// NewLoadBalancedReverseProxy returns a collection of Upstreams that are to be loadbalanced.
|
||||||
func NewLoadBalancedReverseProxy(lb *LoadBalanced, state *State) error {
|
func NewLoadBalancedReverseProxy(lb *LoadBalanced, state *State, ctx caddy2.Context) error {
|
||||||
// set defaults
|
// set defaults
|
||||||
if lb.NoHealthyUpstreamsMessage == "" {
|
if lb.NoHealthyUpstreamsMessage == "" {
|
||||||
lb.NoHealthyUpstreamsMessage = msgNoHealthyUpstreams
|
lb.NoHealthyUpstreamsMessage = msgNoHealthyUpstreams
|
||||||
|
@ -115,7 +115,7 @@ func NewLoadBalancedReverseProxy(lb *LoadBalanced, state *State) error {
|
||||||
|
|
||||||
if uc.CircuitBreaker != nil {
|
if uc.CircuitBreaker != nil {
|
||||||
if _, err := caddy2.GetModule(cbModule); err == nil {
|
if _, err := caddy2.GetModule(cbModule); err == nil {
|
||||||
val, err := caddy2.LoadModule(cbModule, uc.CircuitBreaker)
|
val, err := ctx.LoadModule(cbModule, uc.CircuitBreaker)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cbv, ok := val.(CircuitBreaker)
|
cbv, ok := val.(CircuitBreaker)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
|
@ -29,11 +29,11 @@ type ServerRoute struct {
|
||||||
type RouteList []ServerRoute
|
type RouteList []ServerRoute
|
||||||
|
|
||||||
// Provision sets up all the routes by loading the modules.
|
// Provision sets up all the routes by loading the modules.
|
||||||
func (routes RouteList) Provision() error {
|
func (routes RouteList) Provision(ctx caddy2.Context) error {
|
||||||
for i, route := range routes {
|
for i, route := range routes {
|
||||||
// matchers
|
// matchers
|
||||||
for modName, rawMsg := range route.Matchers {
|
for modName, rawMsg := range route.Matchers {
|
||||||
val, err := caddy2.LoadModule("http.matchers."+modName, rawMsg)
|
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading matcher module '%s': %v", modName, err)
|
return fmt.Errorf("loading matcher module '%s': %v", modName, err)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func (routes RouteList) Provision() error {
|
||||||
|
|
||||||
// middleware
|
// middleware
|
||||||
for j, rawMsg := range route.Apply {
|
for j, rawMsg := range route.Apply {
|
||||||
mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
|
mid, err := ctx.LoadModuleInline("middleware", "http.middleware", rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
|
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func (routes RouteList) Provision() error {
|
||||||
|
|
||||||
// responder
|
// responder
|
||||||
if route.Respond != nil {
|
if route.Respond != nil {
|
||||||
resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
|
resp, err := ctx.LoadModuleInline("responder", "http.responders", route.Respond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading responder module: %v", err)
|
return fmt.Errorf("loading responder module: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,10 @@ func (m *acmeManagerMaker) newManager(interactive bool) (certmagic.Manager, erro
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *acmeManagerMaker) Provision() error {
|
func (m *acmeManagerMaker) Provision(ctx caddy2.Context) error {
|
||||||
// DNS providers
|
// DNS providers
|
||||||
if m.Challenges.DNS != nil {
|
if m.Challenges.DNS != nil {
|
||||||
val, err := caddy2.LoadModuleInline("provider", "tls.dns", m.Challenges.DNS)
|
val, err := ctx.LoadModuleInline("provider", "tls.dns", m.Challenges.DNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading TLS storage module: %s", err)
|
return fmt.Errorf("loading TLS storage module: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func (m *acmeManagerMaker) Provision() error {
|
||||||
|
|
||||||
// policy-specific storage implementation
|
// policy-specific storage implementation
|
||||||
if m.Storage != nil {
|
if m.Storage != nil {
|
||||||
val, err := caddy2.LoadModuleInline("system", "caddy.storage", m.Storage)
|
val, err := ctx.LoadModuleInline("system", "caddy.storage", m.Storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading TLS storage module: %s", err)
|
return fmt.Errorf("loading TLS storage module: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -93,10 +93,10 @@ func (m *acmeManagerMaker) setDefaults() {
|
||||||
// makeCertMagicConfig converts m into a certmagic.Config, because
|
// makeCertMagicConfig converts m into a certmagic.Config, because
|
||||||
// this is a special case where the default manager is the certmagic
|
// this is a special case where the default manager is the certmagic
|
||||||
// Config and not a separate manager.
|
// Config and not a separate manager.
|
||||||
func (m *acmeManagerMaker) makeCertMagicConfig() certmagic.Config {
|
func (m *acmeManagerMaker) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config {
|
||||||
storage := m.storage
|
storage := m.storage
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
storage = caddy2.GetStorage()
|
storage = ctx.Storage()
|
||||||
}
|
}
|
||||||
|
|
||||||
var ond *certmagic.OnDemandConfig
|
var ond *certmagic.OnDemandConfig
|
||||||
|
|
|
@ -18,11 +18,11 @@ type ConnectionPolicies []*ConnectionPolicy
|
||||||
// TLSConfig converts the group of policies to a standard-lib-compatible
|
// TLSConfig converts the group of policies to a standard-lib-compatible
|
||||||
// TLS configuration which selects the first matching policy based on
|
// TLS configuration which selects the first matching policy based on
|
||||||
// the ClientHello.
|
// the ClientHello.
|
||||||
func (cp ConnectionPolicies) TLSConfig(handle caddy2.Handle) (*tls.Config, error) {
|
func (cp ConnectionPolicies) TLSConfig(ctx caddy2.Context) (*tls.Config, error) {
|
||||||
// connection policy matchers
|
// connection policy matchers
|
||||||
for i, pol := range cp {
|
for i, pol := range cp {
|
||||||
for modName, rawMsg := range pol.MatchersRaw {
|
for modName, rawMsg := range pol.MatchersRaw {
|
||||||
val, err := caddy2.LoadModule("tls.handshake_match."+modName, rawMsg)
|
val, err := ctx.LoadModule("tls.handshake_match."+modName, rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
|
return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ func (cp ConnectionPolicies) TLSConfig(handle caddy2.Handle) (*tls.Config, error
|
||||||
|
|
||||||
// pre-build standard TLS configs so we don't have to at handshake-time
|
// pre-build standard TLS configs so we don't have to at handshake-time
|
||||||
for i := range cp {
|
for i := range cp {
|
||||||
err := cp[i].buildStandardTLSConfig(handle)
|
err := cp[i].buildStandardTLSConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
return nil, fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,8 @@ type ConnectionPolicy struct {
|
||||||
stdTLSConfig *tls.Config
|
stdTLSConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
|
func (cp *ConnectionPolicy) buildStandardTLSConfig(ctx caddy2.Context) error {
|
||||||
tlsAppIface, err := handle.App("tls")
|
tlsAppIface, err := ctx.App("tls")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting tls app: %v", err)
|
return fmt.Errorf("getting tls app: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,13 @@ type TLS struct {
|
||||||
|
|
||||||
certificateLoaders []CertificateLoader
|
certificateLoaders []CertificateLoader
|
||||||
certCache *certmagic.Cache
|
certCache *certmagic.Cache
|
||||||
|
ctx caddy2.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision sets up the configuration for the TLS app.
|
// Provision sets up the configuration for the TLS app.
|
||||||
func (t *TLS) Provision() error {
|
func (t *TLS) Provision(ctx caddy2.Context) error {
|
||||||
|
t.ctx = ctx
|
||||||
|
|
||||||
// set up the certificate cache
|
// set up the certificate cache
|
||||||
// TODO: this makes a new cache every time; better to only make a new
|
// TODO: this makes a new cache every time; better to only make a new
|
||||||
// cache (or even better, add/remove only what is necessary) if the
|
// cache (or even better, add/remove only what is necessary) if the
|
||||||
|
@ -41,7 +44,7 @@ func (t *TLS) Provision() error {
|
||||||
})
|
})
|
||||||
|
|
||||||
for i, ap := range t.Automation.Policies {
|
for i, ap := range t.Automation.Policies {
|
||||||
val, err := caddy2.LoadModuleInline("module", "tls.management", ap.Management)
|
val, err := ctx.LoadModuleInline("module", "tls.management", ap.Management)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading TLS automation management module: %s", err)
|
return fmt.Errorf("loading TLS automation management module: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +57,7 @@ func (t *TLS) Provision() error {
|
||||||
if modName == automateKey {
|
if modName == automateKey {
|
||||||
continue // special case; these will be loaded in later
|
continue // special case; these will be loaded in later
|
||||||
}
|
}
|
||||||
val, err := caddy2.LoadModule("tls.certificates."+modName, rawMsg)
|
val, err := ctx.LoadModule("tls.certificates."+modName, rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading certificate module '%s': %s", modName, err)
|
return fmt.Errorf("loading certificate module '%s': %s", modName, err)
|
||||||
}
|
}
|
||||||
|
@ -65,7 +68,7 @@ func (t *TLS) Provision() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start activates the TLS module.
|
// Start activates the TLS module.
|
||||||
func (t *TLS) Start(handle caddy2.Handle) error {
|
func (t *TLS) Start() error {
|
||||||
// load manual/static (unmanaged) certificates
|
// load manual/static (unmanaged) certificates
|
||||||
for _, loader := range t.certificateLoaders {
|
for _, loader := range t.certificateLoaders {
|
||||||
certs, err := loader.LoadCertificates()
|
certs, err := loader.LoadCertificates()
|
||||||
|
@ -73,7 +76,7 @@ func (t *TLS) Start(handle caddy2.Handle) error {
|
||||||
return fmt.Errorf("loading certificates: %v", err)
|
return fmt.Errorf("loading certificates: %v", err)
|
||||||
}
|
}
|
||||||
magic := certmagic.New(t.certCache, certmagic.Config{
|
magic := certmagic.New(t.certCache, certmagic.Config{
|
||||||
Storage: caddy2.GetStorage(),
|
Storage: t.ctx.Storage(),
|
||||||
})
|
})
|
||||||
for _, cert := range certs {
|
for _, cert := range certs {
|
||||||
err := magic.CacheUnmanagedTLSCertificate(cert)
|
err := magic.CacheUnmanagedTLSCertificate(cert)
|
||||||
|
@ -114,7 +117,7 @@ func (t *TLS) Stop() error {
|
||||||
func (t *TLS) Manage(names []string) error {
|
func (t *TLS) Manage(names []string) error {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
ap := t.getAutomationPolicyForName(name)
|
ap := t.getAutomationPolicyForName(name)
|
||||||
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
|
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig(t.ctx))
|
||||||
err := magic.Manage([]string{name})
|
err := magic.Manage([]string{name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("automate: manage %s: %v", name, err)
|
return fmt.Errorf("automate: manage %s: %v", name, err)
|
||||||
|
@ -130,13 +133,13 @@ func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ap := t.getAutomationPolicyForName(r.Host)
|
ap := t.getAutomationPolicyForName(r.Host)
|
||||||
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
|
magic := certmagic.New(t.certCache, ap.makeCertMagicConfig(t.ctx))
|
||||||
return magic.HandleHTTPChallenge(w, r)
|
return magic.HandleHTTPChallenge(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLS) getConfigForName(name string) (certmagic.Config, error) {
|
func (t *TLS) getConfigForName(name string) (certmagic.Config, error) {
|
||||||
ap := t.getAutomationPolicyForName(name)
|
ap := t.getAutomationPolicyForName(name)
|
||||||
return ap.makeCertMagicConfig(), nil
|
return ap.makeCertMagicConfig(t.ctx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLS) getAutomationPolicyForName(name string) AutomationPolicy {
|
func (t *TLS) getAutomationPolicyForName(name string) AutomationPolicy {
|
||||||
|
@ -178,12 +181,12 @@ type AutomationPolicy struct {
|
||||||
management ManagerMaker
|
management ManagerMaker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ap AutomationPolicy) makeCertMagicConfig() certmagic.Config {
|
func (ap AutomationPolicy) makeCertMagicConfig(ctx caddy2.Context) certmagic.Config {
|
||||||
// default manager (ACME) is a special case because of how CertMagic is designed
|
// default manager (ACME) is a special case because of how CertMagic is designed
|
||||||
// TODO: refactor certmagic so that ACME manager is not a special case by extracting
|
// TODO: refactor certmagic so that ACME manager is not a special case by extracting
|
||||||
// its config fields out of the certmagic.Config struct, or something...
|
// its config fields out of the certmagic.Config struct, or something...
|
||||||
if acmeMgmt, ok := ap.management.(*acmeManagerMaker); ok {
|
if acmeMgmt, ok := ap.management.(*acmeManagerMaker); ok {
|
||||||
return acmeMgmt.makeCertMagicConfig()
|
return acmeMgmt.makeCertMagicConfig(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return certmagic.Config{
|
return certmagic.Config{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user