summaryrefslogtreecommitdiffhomepage
path: root/caddy.go
blob: 36c9239fc3591656f8f9e74e31cf6bb5fe999f90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package caddy2

import (
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// Start runs Caddy with the given config.
func Start(cfg 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()

	// prepare the config for use
	cfg.runners = make(map[string]Runner)
	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 in case this
	// function returns an error, we need to put
	// the "old" one back where we found it
	var err error
	oldModuleInstances := moduleInstances
	defer func() {
		if err != nil {
			moduleInstances = oldModuleInstances
		}
	}()
	moduleInstances = make(map[string][]interface{})

	// load (decode) each runner module
	for modName, rawMsg := range cfg.Modules {
		val, err := LoadModule(modName, rawMsg)
		if err != nil {
			return fmt.Errorf("loading module '%s': %v", modName, err)
		}
		cfg.runners[modName] = val.(Runner)
	}

	// start the new runners
	for name, r := range cfg.runners {
		err := r.Run()
		if err != nil {
			// TODO: If any one has an error, stop the others
			return fmt.Errorf("%s module: %v", name, err)
		}
	}

	// shut down down the old runners
	currentCfgMu.Lock()
	if currentCfg != nil {
		for name, r := range currentCfg.runners {
			err := r.Cancel()
			if err != nil {
				log.Printf("[ERROR] cancel %s: %v", name, err)
			}
		}
	}
	oldCfg := currentCfg
	currentCfg = &cfg
	currentCfgMu.Unlock()

	// invoke unload callbacks on old configuration
	for modName := range oldModuleInstances {
		mod, err := GetModule(modName)
		if err != nil {
			return err
		}
		if mod.OnUnload != nil {
			var unloadingState interface{}
			if oldCfg != nil {
				unloadingState = oldCfg.moduleStates[modName]
			}
			err := mod.OnUnload(unloadingState)
			if err != nil {
				log.Printf("[ERROR] module OnUnload: %s: %v", modName, err)
				continue
			}
		}
	}

	// invoke load callbacks on new configuration
	for modName, instances := range moduleInstances {
		mod, err := GetModule(modName)
		if err != nil {
			return err
		}
		if mod.OnLoad != nil {
			var priorState interface{}
			if oldCfg != nil {
				priorState = oldCfg.moduleStates[modName]
			}
			modState, err := mod.OnLoad(instances, priorState)
			if err != nil {
				return fmt.Errorf("module OnLoad: %s: %v", modName, err)
			}
			if modState != nil {
				cfg.moduleStates[modName] = modState
			}
		}
	}

	// shut down listeners that are no longer being used
	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
}

// Runner is a thing that Caddy runs.
type Runner interface {
	Run() error
	Cancel() error
}

// Config represents a Caddy configuration.
type Config struct {
	TestVal string                     `json:"testval"`
	Modules map[string]json.RawMessage `json:"modules"`

	// runners stores the decoded Modules values,
	// keyed by module name.
	runners map[string]Runner

	// moduleStates stores the optional "global" state
	// values of every module used by this configuration,
	// keyed by module name.
	moduleStates map[string]interface{}
}

// Duration is a JSON-string-unmarshable duration type.
type Duration time.Duration

// UnmarshalJSON satisfies json.Unmarshaler.
func (d *Duration) UnmarshalJSON(b []byte) error {
	dd, err := time.ParseDuration(strings.Trim(string(b), `"`))
	if err != nil {
		return err
	}
	cd := Duration(dd)
	d = &cd
	return nil
}

// CtxKey is a value type for use with context.WithValue.
type CtxKey string

// currentCfg is the currently-loaded configuration.
var (
	currentCfg   *Config
	currentCfgMu sync.Mutex
)

// 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
var startMu sync.Mutex