@marcel91200 / caddy Mirror · read-only / commits / b780f0f4
Fork 0

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 +}