Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
Standardize exit codes and improve shutdown handling; update gitignore
Matthew Holt committed
Jul 12, 2019 at 10:07 UTC
b780f0f49b191a6724b7ec54aa62a97d23977231
7 files changed
+242
-37
.gitignore
+2
@@ -1,4 +1,6 @@
1
_gitignore/
2
+*.log
3
+Caddyfile
4
5
# artifacts from pprof tooling
6
*.prof
caddy.go
+36
-10
@@ -150,21 +150,46 @@ func Run(newCfg *Config) error {
150
currentCfg = newCfg
151
152
// Stop, Cleanup each old app
153
- if oldCfg != nil {
154
- for name, a := range oldCfg.apps {
155
- err := a.Stop()
156
- if err != nil {
157
- log.Printf("[ERROR] stop %s: %v", name, err)
158
- }
159
- }
153
+ unsyncedStop(oldCfg)
154
161
- // clean up all old modules
162
- oldCfg.cancelFunc()
163
- }
155
+ return nil
156
+}
157
158
+// Stop stops running the current configuration.
159
+// It is the antithesis of Run(). This function
160
+// will log any errors that occur during the
161
+// stopping of individual apps and continue to
162
+// stop the others.
163
+func Stop() error {
164
+ currentCfgMu.Lock()
165
+ defer currentCfgMu.Unlock()
166
+ unsyncedStop(currentCfg)
167
+ currentCfg = nil
168
return nil
169
}
170
171
+// unsyncedStop stops oldCfg from running, but if
172
+// applicable, you need to acquire locks yourself.
173
+// It is a no-op if oldCfg is nil. If any app
174
+// returns an error when stopping, it is logged
175
+// and the function continues with the next app.
176
+func unsyncedStop(oldCfg *Config) {
177
+ if oldCfg == nil {
178
+ return
179
+ }
180
+
181
+ // stop each app
182
+ for name, a := range oldCfg.apps {
183
+ err := a.Stop()
184
+ if err != nil {
185
+ log.Printf("[ERROR] stop %s: %v", name, err)
186
+ }
187
+ }
188
+
189
+ // clean up all old modules
190
+ oldCfg.cancelFunc()
191
+}
192
+
193
// Duration is a JSON-string-unmarshable duration type.
194
type Duration time.Duration
195
@@ -199,6 +224,7 @@ func GoModule() *debug.Module {
224
}
225
226
// goModule is the name of this Go module.
227
+// TODO: we should be able to find this at runtime, see https://github.com/golang/go/issues/29228
228
const goModule = "github.com/caddyserver/caddy/v2"
229
230
// CtxKey is a value type for use with context.WithValue.
cmd/commands.go
+39
-25
@@ -44,7 +44,8 @@ func cmdStart() (int, error) {
44
// it is ready to confirm that it has successfully started
45
ln, err := net.Listen("tcp", "127.0.0.1:0")
46
if err != nil {
47
- return 1, fmt.Errorf("opening listener for success confirmation: %v", err)
47
+ return caddy.ExitCodeFailedStartup,
48
+ fmt.Errorf("opening listener for success confirmation: %v", err)
49
}
50
defer ln.Close()
51
@@ -63,7 +64,8 @@ func cmdStart() (int, error) {
64
}
65
stdinpipe, err := cmd.StdinPipe()
66
if err != nil {
66
- return 1, fmt.Errorf("creating stdin pipe: %v", err)
67
+ return caddy.ExitCodeFailedStartup,
68
+ fmt.Errorf("creating stdin pipe: %v", err)
69
}
70
cmd.Stdout = os.Stdout
71
cmd.Stderr = os.Stderr
@@ -72,7 +74,7 @@ func cmdStart() (int, error) {
74
expect := make([]byte, 32)
75
_, err = rand.Read(expect)
76
if err != nil {
75
- return 1, fmt.Errorf("generating random confirmation bytes: %v", err)
77
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("generating random confirmation bytes: %v", err)
78
}
79
80
// begin writing the confirmation bytes to the child's
@@ -87,7 +89,7 @@ func cmdStart() (int, error) {
89
// start the process
90
err = cmd.Start()
91
if err != nil {
90
- return 1, fmt.Errorf("starting caddy process: %v", err)
92
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy process: %v", err)
93
}
94
95
// there are two ways we know we're done: either
@@ -125,10 +127,11 @@ func cmdStart() (int, error) {
127
case <-success:
128
fmt.Println("Successfully started Caddy")
129
case err := <-exit:
128
- return 1, fmt.Errorf("caddy process exited with error: %v", err)
130
+ return caddy.ExitCodeFailedStartup,
131
+ fmt.Errorf("caddy process exited with error: %v", err)
132
}
133
131
- return 0, nil
134
+ return caddy.ExitCodeSuccess, nil
135
}
136
137
func cmdRun() (int, error) {
@@ -144,7 +147,8 @@ func cmdRun() (int, error) {
147
var err error
148
config, err = ioutil.ReadFile(*runCmdConfigFlag)
149
if err != nil {
147
- return 1, fmt.Errorf("reading config file: %v", err)
150
+ return caddy.ExitCodeFailedStartup,
151
+ fmt.Errorf("reading config file: %v", err)
152
}
153
}
154
@@ -156,7 +160,8 @@ func cmdRun() (int, error) {
160
// start the admin endpoint along with any initial config
161
err := caddy.StartAdmin(config)
162
if err != nil {
159
- return 1, fmt.Errorf("starting caddy administration endpoint: %v", err)
163
+ return caddy.ExitCodeFailedStartup,
164
+ fmt.Errorf("starting caddy administration endpoint: %v", err)
165
}
166
defer caddy.StopAdmin()
167
@@ -165,16 +170,19 @@ func cmdRun() (int, error) {
170
if *runCmdPingbackFlag != "" {
171
confirmationBytes, err := ioutil.ReadAll(os.Stdin)
172
if err != nil {
168
- return 1, fmt.Errorf("reading confirmation bytes from stdin: %v", err)
173
+ return caddy.ExitCodeFailedStartup,
174
+ fmt.Errorf("reading confirmation bytes from stdin: %v", err)
175
}
176
conn, err := net.Dial("tcp", *runCmdPingbackFlag)
177
if err != nil {
172
- return 1, fmt.Errorf("dialing confirmation address: %v", err)
178
+ return caddy.ExitCodeFailedStartup,
179
+ fmt.Errorf("dialing confirmation address: %v", err)
180
}
181
defer conn.Close()
182
_, err = conn.Write(confirmationBytes)
183
if err != nil {
177
- return 1, fmt.Errorf("writing confirmation bytes to %s: %v", *runCmdPingbackFlag, err)
184
+ return caddy.ExitCodeFailedStartup,
185
+ fmt.Errorf("writing confirmation bytes to %s: %v", *runCmdPingbackFlag, err)
186
}
187
}
188
@@ -184,7 +192,7 @@ func cmdRun() (int, error) {
192
func cmdStop() (int, error) {
193
processList, err := ps.Processes()
194
if err != nil {
187
- return 1, fmt.Errorf("listing processes: %v", err)
195
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
196
}
197
thisProcName := filepath.Base(os.Args[0])
198
var found bool
@@ -195,15 +203,15 @@ func cmdStop() (int, error) {
203
fmt.Printf("pid=%d\n", p.Pid())
204
fmt.Printf("Graceful stop...")
205
if err := gracefullyStopProcess(p.Pid()); err != nil {
198
- return 1, err
206
+ return caddy.ExitCodeFailedStartup, err
207
}
208
}
209
}
210
if !found {
203
- return 1, fmt.Errorf("Caddy is not running")
211
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running")
212
}
213
fmt.Println(" success")
206
- return 0, nil
214
+ return caddy.ExitCodeSuccess, nil
215
}
216
217
func cmdReload() (int, error) {
@@ -214,13 +222,15 @@ func cmdReload() (int, error) {
222
223
// a configuration is required
224
if *reloadCmdConfigFlag == "" {
217
- return 1, fmt.Errorf("no configuration to load (use --config)")
225
+ return caddy.ExitCodeFailedStartup,
226
+ fmt.Errorf("no configuration to load (use --config)")
227
}
228
229
// load the configuration file
230
config, err := ioutil.ReadFile(*reloadCmdConfigFlag)
231
if err != nil {
223
- return 1, fmt.Errorf("reading config file: %v", err)
232
+ return caddy.ExitCodeFailedStartup,
233
+ fmt.Errorf("reading config file: %v", err)
234
}
235
236
// get the address of the admin listener and craft endpoint URL
@@ -231,7 +241,8 @@ func cmdReload() (int, error) {
241
}
242
err = json.Unmarshal(config, &tmpStruct)
243
if err != nil {
234
- return 1, fmt.Errorf("unmarshaling admin listener address from config: %v", err)
244
+ return caddy.ExitCodeFailedStartup,
245
+ fmt.Errorf("unmarshaling admin listener address from config: %v", err)
246
}
247
adminAddr = tmpStruct.Admin.Listen
248
}
@@ -243,7 +254,8 @@ func cmdReload() (int, error) {
254
// send the configuration to the instance
255
resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config))
256
if err != nil {
246
- return 1, fmt.Errorf("sending configuration to instance: %v", err)
257
+ return caddy.ExitCodeFailedStartup,
258
+ fmt.Errorf("sending configuration to instance: %v", err)
259
}
260
defer resp.Body.Close()
261
@@ -251,12 +263,14 @@ func cmdReload() (int, error) {
263
if resp.StatusCode >= 400 {
264
respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
265
if err != nil {
254
- return 1, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
266
+ return caddy.ExitCodeFailedStartup,
267
+ fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
268
}
256
- return 1, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
269
+ return caddy.ExitCodeFailedStartup,
270
+ fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
271
}
272
259
- return 0, nil
273
+ return caddy.ExitCodeSuccess, nil
274
}
275
276
func cmdVersion() (int, error) {
@@ -267,19 +281,19 @@ func cmdVersion() (int, error) {
281
} else {
282
fmt.Println(goModule.Version)
283
}
270
- return 0, nil
284
+ return caddy.ExitCodeSuccess, nil
285
}
286
287
func cmdListModules() (int, error) {
288
for _, m := range caddy.Modules() {
289
fmt.Println(m)
290
}
277
- return 0, nil
291
+ return caddy.ExitCodeSuccess, nil
292
}
293
294
func cmdEnviron() (int, error) {
295
for _, v := range os.Environ() {
296
fmt.Println(v)
297
}
284
- return 0, nil
298
+ return caddy.ExitCodeSuccess, nil
299
}
cmd/main.go
+7
-2
@@ -23,10 +23,15 @@ import (
23
"log"
24
"net"
25
"os"
26
+
27
+ "github.com/caddyserver/caddy/v2"
28
)
29
28
-// Main executes the main function of the caddy command.
30
+// Main implements the main function of the caddy command.
31
+// Call this if Caddy is to be the main() if your program.
32
func Main() {
33
+ caddy.TrapSignals()
34
+
35
if len(os.Args) <= 1 {
36
fmt.Println(usageString())
37
return
@@ -35,7 +40,7 @@ func Main() {
40
subcommand, ok := commands[os.Args[1]]
41
if !ok {
42
fmt.Printf("%q is not a valid command\n", os.Args[1])
38
- os.Exit(2)
43
+ os.Exit(caddy.ExitCodeFailedStartup)
44
}
45
46
if exitCode, err := subcommand(); err != nil {
sigtrap.go
new
+82
@@ -0,0 +1,82 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddy
16
+
17
+import (
18
+ "log"
19
+ "os"
20
+ "os/signal"
21
+
22
+ "github.com/mholt/certmagic"
23
+)
24
+
25
+// TrapSignals create signal/interrupt handlers as best it can for the
26
+// current OS. This is a rather invasive function to call in a Go program
27
+// that captures signals already, so in that case it would be better to
28
+// implement these handlers yourself.
29
+func TrapSignals() {
30
+ trapSignalsCrossPlatform()
31
+ trapSignalsPosix()
32
+}
33
+
34
+// trapSignalsCrossPlatform captures SIGINT or interrupt (depending
35
+// on the OS), which initiates a graceful shutdown. A second SIGINT
36
+// or interrupt will forcefully exit the process immediately.
37
+func trapSignalsCrossPlatform() {
38
+ go func() {
39
+ shutdown := make(chan os.Signal, 1)
40
+ signal.Notify(shutdown, os.Interrupt)
41
+
42
+ for i := 0; true; i++ {
43
+ <-shutdown
44
+
45
+ if i > 0 {
46
+ log.Println("[INFO] SIGINT: Force quit")
47
+ os.Exit(ExitCodeForceQuit)
48
+ }
49
+
50
+ log.Println("[INFO] SIGINT: Shutting down")
51
+ go gracefulStop("SIGINT")
52
+ }
53
+ }()
54
+}
55
+
56
+// gracefulStop exits the process as gracefully as possible.
57
+func gracefulStop(sigName string) {
58
+ exitCode := ExitCodeSuccess
59
+
60
+ // first stop all the apps
61
+ err := Stop()
62
+ if err != nil {
63
+ log.Printf("[ERROR] %s stop: %v", sigName, err)
64
+ exitCode = ExitCodeFailedQuit
65
+ }
66
+
67
+ // always, always, always try to clean up locks
68
+ certmagic.CleanUpOwnLocks()
69
+
70
+ log.Printf("[INFO] %s: Shutdown done", sigName)
71
+ os.Exit(exitCode)
72
+}
73
+
74
+// Exit codes. Generally, you will want to avoid
75
+// automatically restarting the process if the
76
+// exit code is 1.
77
+const (
78
+ ExitCodeSuccess = iota
79
+ ExitCodeFailedStartup
80
+ ExitCodeForceQuit
81
+ ExitCodeFailedQuit
82
+)
sigtrap_nonposix.go
new
+19
@@ -0,0 +1,19 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+// +build windows plan9 nacl js
16
+
17
+package caddy
18
+
19
+func trapSignalsPosix() {}
sigtrap_posix.go
new
+57
@@ -0,0 +1,57 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+// +build !windows,!plan9,!nacl,!js
16
+
17
+package caddy
18
+
19
+import (
20
+ "log"
21
+ "os"
22
+ "os/signal"
23
+ "syscall"
24
+
25
+ "github.com/mholt/certmagic"
26
+)
27
+
28
+// trapSignalsPosix captures POSIX-only signals.
29
+func trapSignalsPosix() {
30
+ go func() {
31
+ sigchan := make(chan os.Signal, 1)
32
+ signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
33
+
34
+ for sig := range sigchan {
35
+ switch sig {
36
+ case syscall.SIGQUIT:
37
+ log.Println("[INFO] SIGQUIT: Quitting process immediately")
38
+ certmagic.CleanUpOwnLocks() // try to clean up locks anyway, it's important
39
+ os.Exit(ExitCodeForceQuit)
40
+
41
+ case syscall.SIGTERM:
42
+ log.Println("[INFO] SIGTERM: Shutting down apps then terminating")
43
+ gracefulStop("SIGTERM")
44
+
45
+ case syscall.SIGUSR1:
46
+ log.Println("[INFO] SIGUSR1: Not implemented")
47
+
48
+ case syscall.SIGUSR2:
49
+ log.Println("[INFO] SIGUSR2: Not implemented")
50
+
51
+ case syscall.SIGHUP:
52
+ // ignore; this signal is sometimes sent outside of the user's control
53
+ log.Println("[INFO] SIGHUP: Not implemented")
54
+ }
55
+ }
56
+ }()
57
+}