mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-13 22:51:08 -05:00
core: More refactoring, code cleanup, docs
This commit is contained in:
parent
41c4484222
commit
5b1962303d
3 changed files with 227 additions and 192 deletions
213
caddy/caddy.go
213
caddy/caddy.go
|
@ -1,3 +1,16 @@
|
||||||
|
// Package caddy implements the Caddy web server as a service.
|
||||||
|
//
|
||||||
|
// To use this package, follow a few simple steps:
|
||||||
|
//
|
||||||
|
// 1. Set the AppName and AppVersion variables.
|
||||||
|
// 2. Call LoadCaddyfile() to get the Caddyfile (it
|
||||||
|
// might have been piped in as part of a restart).
|
||||||
|
// You should pass in your own Caddyfile loader.
|
||||||
|
// 3. Call caddy.Start() to start Caddy, caddy.Stop()
|
||||||
|
// to stop it, or caddy.Restart() to restart it.
|
||||||
|
//
|
||||||
|
// You should use caddy.Wait() to wait for all Caddy servers
|
||||||
|
// to quit before your process exits.
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,16 +22,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddy/letsencrypt"
|
|
||||||
"github.com/mholt/caddy/server"
|
"github.com/mholt/caddy/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,14 +79,6 @@ const (
|
||||||
DefaultRoot = "."
|
DefaultRoot = "."
|
||||||
)
|
)
|
||||||
|
|
||||||
// caddyfileGob maps bind address to index of the file descriptor
|
|
||||||
// in the Files array passed to the child process. It also contains
|
|
||||||
// the caddyfile contents.
|
|
||||||
type caddyfileGob struct {
|
|
||||||
ListenerFds map[string]uintptr
|
|
||||||
Caddyfile []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts Caddy with the given Caddyfile. If cdyfile
|
// Start starts Caddy with the given Caddyfile. If cdyfile
|
||||||
// is nil or the process is forked from a parent as part of
|
// is nil or the process is forked from a parent as part of
|
||||||
// a graceful restart, Caddy will check to see if Caddyfile
|
// a graceful restart, Caddy will check to see if Caddyfile
|
||||||
|
@ -216,28 +215,7 @@ func startServers(groupings Group) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isLocalhost returns true if the string looks explicitly like a localhost address.
|
// Stop stops all servers. It blocks until they are all stopped.
|
||||||
func isLocalhost(s string) bool {
|
|
||||||
return s == "localhost" || s == "::1" || strings.HasPrefix(s, "127.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkFdlimit issues a warning if the OS max file descriptors is below a recommended minimum.
|
|
||||||
func checkFdlimit() {
|
|
||||||
const min = 4096
|
|
||||||
|
|
||||||
// Warn if ulimit is too low for production sites
|
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
|
||||||
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
|
|
||||||
if err == nil {
|
|
||||||
// Note that an error here need not be reported
|
|
||||||
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
|
|
||||||
if err == nil && lim < min {
|
|
||||||
fmt.Printf("Warning: File descriptor limit %d is too low for production sites. At least %d is recommended. Set with \"ulimit -n %d\".\n", lim, min, min)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stop() error {
|
func Stop() error {
|
||||||
serversMu.Lock()
|
serversMu.Lock()
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
|
@ -247,100 +225,6 @@ func Stop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart restarts the entire application; gracefully with zero
|
|
||||||
// downtime if on a POSIX-compatible system, or forcefully if on
|
|
||||||
// Windows but with imperceptibly-short downtime.
|
|
||||||
//
|
|
||||||
// The restarted application will use newCaddyfile as its input
|
|
||||||
// configuration. If newCaddyfile is nil, the current (existing)
|
|
||||||
// Caddyfile configuration will be used.
|
|
||||||
func Restart(newCaddyfile Input) error {
|
|
||||||
if newCaddyfile == nil {
|
|
||||||
caddyfileMu.Lock()
|
|
||||||
newCaddyfile = caddyfile
|
|
||||||
caddyfileMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
err := Stop()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = Start(newCaddyfile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(os.Args) == 0 { // this should never happen, but just in case...
|
|
||||||
os.Args = []string{""}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the child that it's a restart
|
|
||||||
os.Setenv("CADDY_RESTART", "true")
|
|
||||||
|
|
||||||
// Prepare our payload to the child process
|
|
||||||
cdyfileGob := caddyfileGob{
|
|
||||||
ListenerFds: make(map[string]uintptr),
|
|
||||||
Caddyfile: newCaddyfile.Body(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare a pipe to the fork's stdin so it can get the Caddyfile
|
|
||||||
rpipe, wpipe, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare a pipe that the child process will use to communicate
|
|
||||||
// its success or failure with us, the parent
|
|
||||||
sigrpipe, sigwpipe, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass along current environment and file descriptors to child.
|
|
||||||
// Ordering here is very important: stdin, stdout, stderr, sigpipe,
|
|
||||||
// and then the listener file descriptors (in order).
|
|
||||||
fds := []uintptr{rpipe.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), sigwpipe.Fd()}
|
|
||||||
|
|
||||||
// Now add file descriptors of the sockets
|
|
||||||
serversMu.Lock()
|
|
||||||
for i, s := range servers {
|
|
||||||
fds = append(fds, s.ListenerFd())
|
|
||||||
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
|
|
||||||
}
|
|
||||||
serversMu.Unlock()
|
|
||||||
|
|
||||||
// Fork the process with the current environment and file descriptors
|
|
||||||
execSpec := &syscall.ProcAttr{
|
|
||||||
Env: os.Environ(),
|
|
||||||
Files: fds,
|
|
||||||
}
|
|
||||||
_, err = syscall.ForkExec(os.Args[0], os.Args, execSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feed it the Caddyfile
|
|
||||||
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wpipe.Close()
|
|
||||||
|
|
||||||
// Wait for child process to signal success or fail
|
|
||||||
sigwpipe.Close() // close our copy of the write end of the pipe
|
|
||||||
answer, err := ioutil.ReadAll(sigrpipe)
|
|
||||||
if err != nil || len(answer) == 0 {
|
|
||||||
log.Println("restart: child failed to answer; changes not applied")
|
|
||||||
return incompleteRestartErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Child process is listening now; we can stop all our servers here.
|
|
||||||
return Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait blocks until all servers are stopped.
|
// Wait blocks until all servers are stopped.
|
||||||
func Wait() {
|
func Wait() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -387,19 +271,6 @@ func LoadCaddyfile(loader func() (Input, error)) (cdyfile Input, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caddyfile returns the current Caddyfile
|
|
||||||
func Caddyfile() Input {
|
|
||||||
caddyfileMu.Lock()
|
|
||||||
defer caddyfileMu.Unlock()
|
|
||||||
return caddyfile
|
|
||||||
}
|
|
||||||
|
|
||||||
// isRestart returns whether this process is, according
|
|
||||||
// to env variables, a fork as part of a graceful restart.
|
|
||||||
func isRestart() bool {
|
|
||||||
return os.Getenv("CADDY_RESTART") == "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CaddyfileFromPipe loads the Caddyfile input from f if f is
|
// CaddyfileFromPipe loads the Caddyfile input from f if f is
|
||||||
// not interactive input. f is assumed to be a pipe or stream,
|
// not interactive input. f is assumed to be a pipe or stream,
|
||||||
// such as os.Stdin. If f is not a pipe, no error is returned
|
// such as os.Stdin. If f is not a pipe, no error is returned
|
||||||
|
@ -430,6 +301,13 @@ func CaddyfileFromPipe(f *os.File) (Input, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caddyfile returns the current Caddyfile
|
||||||
|
func Caddyfile() Input {
|
||||||
|
caddyfileMu.Lock()
|
||||||
|
defer caddyfileMu.Unlock()
|
||||||
|
return caddyfile
|
||||||
|
}
|
||||||
|
|
||||||
// Input represents a Caddyfile; its contents and file path
|
// Input represents a Caddyfile; its contents and file path
|
||||||
// (which should include the file name at the end of the path).
|
// (which should include the file name at the end of the path).
|
||||||
// If path does not apply (e.g. piped input) you may use
|
// If path does not apply (e.g. piped input) you may use
|
||||||
|
@ -442,52 +320,3 @@ type Input interface {
|
||||||
// Gets the path to the origin file
|
// Gets the path to the origin file
|
||||||
Path() string
|
Path() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyfileInput represents a Caddyfile as input
|
|
||||||
// and is simply a convenient way to implement
|
|
||||||
// the Input interface.
|
|
||||||
type CaddyfileInput struct {
|
|
||||||
Filepath string
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body returns c.Contents.
|
|
||||||
func (c CaddyfileInput) Body() []byte { return c.Contents }
|
|
||||||
|
|
||||||
// Path returns c.Filepath.
|
|
||||||
func (c CaddyfileInput) Path() string { return c.Filepath }
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
letsencrypt.OnRenew = func() error { return Restart(nil) }
|
|
||||||
|
|
||||||
// Trap signals
|
|
||||||
go func() {
|
|
||||||
shutdown, reload := make(chan os.Signal, 1), make(chan os.Signal, 1)
|
|
||||||
signal.Notify(shutdown, os.Interrupt, os.Kill) // quit the process
|
|
||||||
signal.Notify(reload, syscall.SIGUSR1) // reload configuration
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-shutdown:
|
|
||||||
var exitCode int
|
|
||||||
|
|
||||||
serversMu.Lock()
|
|
||||||
errs := server.ShutdownCallbacks(servers)
|
|
||||||
serversMu.Unlock()
|
|
||||||
if len(errs) > 0 {
|
|
||||||
for _, err := range errs {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
exitCode = 1
|
|
||||||
}
|
|
||||||
os.Exit(exitCode)
|
|
||||||
|
|
||||||
case <-reload:
|
|
||||||
err := Restart(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
74
caddy/helpers.go
Normal file
74
caddy/helpers.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/caddy/letsencrypt"
|
||||||
|
"github.com/mholt/caddy/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
letsencrypt.OnRenew = func() error { return Restart(nil) }
|
||||||
|
|
||||||
|
// Trap signals
|
||||||
|
go func() {
|
||||||
|
shutdown, reload := make(chan os.Signal, 1), make(chan os.Signal, 1)
|
||||||
|
signal.Notify(shutdown, os.Interrupt, os.Kill) // quit the process
|
||||||
|
signal.Notify(reload, syscall.SIGUSR1) // reload configuration
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-shutdown:
|
||||||
|
var exitCode int
|
||||||
|
|
||||||
|
serversMu.Lock()
|
||||||
|
errs := server.ShutdownCallbacks(servers)
|
||||||
|
serversMu.Unlock()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
os.Exit(exitCode)
|
||||||
|
|
||||||
|
case <-reload:
|
||||||
|
err := Restart(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLocalhost returns true if the string looks explicitly like a localhost address.
|
||||||
|
func isLocalhost(s string) bool {
|
||||||
|
return s == "localhost" || s == "::1" || strings.HasPrefix(s, "127.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFdlimit issues a warning if the OS max file descriptors is below a recommended minimum.
|
||||||
|
func checkFdlimit() {
|
||||||
|
const min = 4096
|
||||||
|
|
||||||
|
// Warn if ulimit is too low for production sites
|
||||||
|
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||||
|
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
|
||||||
|
if err == nil {
|
||||||
|
// Note that an error here need not be reported
|
||||||
|
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
|
||||||
|
if err == nil && lim < min {
|
||||||
|
fmt.Printf("Warning: File descriptor limit %d is too low for production sites. At least %d is recommended. Set with \"ulimit -n %d\".\n", lim, min, min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
caddy/restart.go
Normal file
132
caddy/restart.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// caddyfileGob maps bind address to index of the file descriptor
|
||||||
|
// in the Files array passed to the child process. It also contains
|
||||||
|
// the caddyfile contents. Used only during graceful restarts.
|
||||||
|
type caddyfileGob struct {
|
||||||
|
ListenerFds map[string]uintptr
|
||||||
|
Caddyfile []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart restarts the entire application; gracefully with zero
|
||||||
|
// downtime if on a POSIX-compatible system, or forcefully if on
|
||||||
|
// Windows but with imperceptibly-short downtime.
|
||||||
|
//
|
||||||
|
// The restarted application will use newCaddyfile as its input
|
||||||
|
// configuration. If newCaddyfile is nil, the current (existing)
|
||||||
|
// Caddyfile configuration will be used.
|
||||||
|
func Restart(newCaddyfile Input) error {
|
||||||
|
if newCaddyfile == nil {
|
||||||
|
caddyfileMu.Lock()
|
||||||
|
newCaddyfile = caddyfile
|
||||||
|
caddyfileMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
err := Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = Start(newCaddyfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Args) == 0 { // this should never happen, but just in case...
|
||||||
|
os.Args = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the child that it's a restart
|
||||||
|
os.Setenv("CADDY_RESTART", "true")
|
||||||
|
|
||||||
|
// Prepare our payload to the child process
|
||||||
|
cdyfileGob := caddyfileGob{
|
||||||
|
ListenerFds: make(map[string]uintptr),
|
||||||
|
Caddyfile: newCaddyfile.Body(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare a pipe to the fork's stdin so it can get the Caddyfile
|
||||||
|
rpipe, wpipe, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare a pipe that the child process will use to communicate
|
||||||
|
// its success or failure with us, the parent
|
||||||
|
sigrpipe, sigwpipe, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass along current environment and file descriptors to child.
|
||||||
|
// Ordering here is very important: stdin, stdout, stderr, sigpipe,
|
||||||
|
// and then the listener file descriptors (in order).
|
||||||
|
fds := []uintptr{rpipe.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), sigwpipe.Fd()}
|
||||||
|
|
||||||
|
// Now add file descriptors of the sockets
|
||||||
|
serversMu.Lock()
|
||||||
|
for i, s := range servers {
|
||||||
|
fds = append(fds, s.ListenerFd())
|
||||||
|
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
|
||||||
|
}
|
||||||
|
serversMu.Unlock()
|
||||||
|
|
||||||
|
// Fork the process with the current environment and file descriptors
|
||||||
|
execSpec := &syscall.ProcAttr{
|
||||||
|
Env: os.Environ(),
|
||||||
|
Files: fds,
|
||||||
|
}
|
||||||
|
_, err = syscall.ForkExec(os.Args[0], os.Args, execSpec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed it the Caddyfile
|
||||||
|
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wpipe.Close()
|
||||||
|
|
||||||
|
// Wait for child process to signal success or fail
|
||||||
|
sigwpipe.Close() // close our copy of the write end of the pipe
|
||||||
|
answer, err := ioutil.ReadAll(sigrpipe)
|
||||||
|
if err != nil || len(answer) == 0 {
|
||||||
|
log.Println("restart: child failed to answer; changes not applied")
|
||||||
|
return incompleteRestartErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child process is listening now; we can stop all our servers here.
|
||||||
|
return Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRestart returns whether this process is, according
|
||||||
|
// to env variables, a fork as part of a graceful restart.
|
||||||
|
func isRestart() bool {
|
||||||
|
return os.Getenv("CADDY_RESTART") == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyfileInput represents a Caddyfile as input
|
||||||
|
// and is simply a convenient way to implement
|
||||||
|
// the Input interface.
|
||||||
|
type CaddyfileInput struct {
|
||||||
|
Filepath string
|
||||||
|
Contents []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns c.Contents.
|
||||||
|
func (c CaddyfileInput) Body() []byte { return c.Contents }
|
||||||
|
|
||||||
|
// Path returns c.Filepath.
|
||||||
|
func (c CaddyfileInput) Path() string { return c.Filepath }
|
Loading…
Reference in a new issue