Commit f26447e2 authored by Matthew Holt's avatar Matthew Holt

Merge branch 'master' into cert-cache

# Conflicts:
#	sigtrap_posix.go
parents 08028714 2de49500
......@@ -16,4 +16,6 @@ Caddyfile
og_static/
.vscode/
\ No newline at end of file
.vscode/
*.bat
\ No newline at end of file
<p align="center">
<a href="https://caddyserver.com"><img src="https://cloud.githubusercontent.com/assets/1128849/25305033/12916fce-2731-11e7-86ec-580d4d31cb16.png" alt="Caddy" width="400"></a>
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36137292-bebc223a-1051-11e8-9a81-4ea9054c96ac.png" alt="Caddy" width="400"></a>
</p>
<h3 align="center">Every Site on HTTPS <!-- Serve Confidently --></h3>
<p align="center">Caddy is a general-purpose HTTP/2 web server that serves HTTPS by default.</p>
......
......@@ -170,10 +170,18 @@ func confLoader(serverType string) (caddy.Input, error) {
return caddy.CaddyfileFromPipe(os.Stdin, serverType)
}
contents, err := ioutil.ReadFile(conf)
if err != nil {
return nil, err
var contents []byte
if strings.Contains(conf, "*") {
// Let caddyfile.doImport logic handle the globbed path
contents = []byte("import " + conf)
} else {
var err error
contents, err = ioutil.ReadFile(conf)
if err != nil {
return nil, err
}
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: conf,
......@@ -221,6 +229,8 @@ func setVersion() {
// setCPU parses string cpu and sets GOMAXPROCS
// according to its value. It accepts either
// a number (e.g. 3) or a percent (e.g. 50%).
// If the percent resolves to less than a single
// GOMAXPROCS, it rounds it up to GOMAXPROCS=1.
func setCPU(cpu string) error {
var numCPU int
......@@ -236,6 +246,9 @@ func setCPU(cpu string) error {
}
percent = float32(pctInt) / 100
numCPU = int(float32(availCPU) * percent)
if numCPU < 1 {
numCPU = 1
}
} else {
// Number
num, err := strconv.Atoi(cpu)
......
......@@ -41,6 +41,7 @@ func TestSetCPU(t *testing.T) {
{"invalid input", currentCPU, true},
{"invalid input%", currentCPU, true},
{"9999", maxCPU, false}, // over available CPU
{"1%", 1, false}, // under a single CPU; assume maxCPU < 100
} {
err := setCPU(test.input)
if test.shouldErr && err == nil {
......
......@@ -499,7 +499,7 @@ footer {
return;
}
}
e.textContent = d.toLocaleString();
e.textContent = d.toLocaleString([], {day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit"});
}
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
timeList.forEach(localizeDatetime);
......
......@@ -148,7 +148,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
case "HEAD":
resp, err = fcgiBackend.Head(env)
case "GET":
resp, err = fcgiBackend.Get(env)
resp, err = fcgiBackend.Get(env, r.Body, contentLength)
case "OPTIONS":
resp, err = fcgiBackend.Options(env)
default:
......
......@@ -460,12 +460,12 @@ func (c *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Res
}
// Get issues a GET request to the fcgi responder.
func (c *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) {
func (c *FCGIClient) Get(p map[string]string, body io.Reader, l int64) (resp *http.Response, err error) {
p["REQUEST_METHOD"] = "GET"
p["CONTENT_LENGTH"] = "0"
p["CONTENT_LENGTH"] = strconv.FormatInt(l, 10)
return c.Request(p, nil)
return c.Request(p, body)
}
// Head issues a HEAD request to the fcgi responder.
......
......@@ -140,7 +140,8 @@ func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[
}
resp, err = fcgi.PostForm(fcgiParams, values)
} else {
resp, err = fcgi.Get(fcgiParams)
rd := bytes.NewReader(data)
resp, err = fcgi.Get(fcgiParams, rd, int64(rd.Len()))
}
default:
......
......@@ -115,6 +115,7 @@ type ResponseBuffer struct {
shouldBuffer func(status int, header http.Header) bool
stream bool
rw http.ResponseWriter
wroteHeader bool
}
// NewResponseBuffer returns a new ResponseBuffer that will
......@@ -152,6 +153,11 @@ func (rb *ResponseBuffer) Header() http.Header {
// upcoming body should be buffered, and then writes
// the header to the response.
func (rb *ResponseBuffer) WriteHeader(status int) {
if rb.wroteHeader {
return
}
rb.wroteHeader = true
rb.status = status
rb.stream = !rb.shouldBuffer(status, rb.header)
if rb.stream {
......@@ -163,6 +169,10 @@ func (rb *ResponseBuffer) WriteHeader(status int) {
// Write writes buf to rb.Buffer if buffering, otherwise
// to the ResponseWriter directly if streaming.
func (rb *ResponseBuffer) Write(buf []byte) (int, error) {
if !rb.wroteHeader {
rb.WriteHeader(http.StatusOK)
}
if rb.stream {
return rb.ResponseWriterWrapper.Write(buf)
}
......@@ -190,6 +200,10 @@ func (rb *ResponseBuffer) CopyHeader() {
// from ~8,200 to ~9,600 on templated files by ensuring that this type
// implements io.ReaderFrom.
func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) {
if !rb.wroteHeader {
rb.WriteHeader(http.StatusOK)
}
if rb.stream {
// first see if we can avoid any allocations at all
if wt, ok := src.(io.WriterTo); ok {
......
......@@ -240,6 +240,7 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
rp.Transport = &h2quic.RoundTripper{
QuicConfig: &quic.Config{
HandshakeTimeout: defaultCryptoHandshakeTimeout,
KeepAlive: true,
},
}
} else if keepalive != http.DefaultMaxIdleConnsPerHost || strings.HasPrefix(target.Scheme, "srv") {
......
......@@ -16,6 +16,7 @@ package requestid
import (
"context"
"log"
"net/http"
"github.com/google/uuid"
......@@ -24,12 +25,29 @@ import (
// Handler is a middleware handler
type Handler struct {
Next httpserver.Handler
Next httpserver.Handler
HeaderName string // (optional) header from which to read an existing ID
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
reqid := uuid.New().String()
c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid)
var reqid uuid.UUID
uuidFromHeader := r.Header.Get(h.HeaderName)
if h.HeaderName != "" && uuidFromHeader != "" {
// use the ID in the header field if it exists
var err error
reqid, err = uuid.Parse(uuidFromHeader)
if err != nil {
log.Printf("[NOTICE] Parsing request ID from %s header: %v", h.HeaderName, err)
reqid = uuid.New()
}
} else {
// otherwise, create a new one
reqid = uuid.New()
}
// set the request ID on the context
c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid.String())
r = r.WithContext(c)
return h.Next.ServeHTTP(w, r)
......
......@@ -15,34 +15,53 @@
package requestid
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestRequestID(t *testing.T) {
request, err := http.NewRequest("GET", "http://localhost/", nil)
func TestRequestIDHandler(t *testing.T) {
handler := Handler{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
value, _ := r.Context().Value(httpserver.RequestIDCtxKey).(string)
if value == "" {
t.Error("Request ID should not be empty")
}
return 0, nil
}),
}
req, err := http.NewRequest("GET", "http://localhost/", nil)
if err != nil {
t.Fatal("Could not create HTTP request:", err)
}
rec := httptest.NewRecorder()
reqid := uuid.New().String()
c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid)
request = request.WithContext(c)
// See caddyhttp/replacer.go
value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string)
handler.ServeHTTP(rec, req)
}
if value == "" {
t.Fatal("Request ID should not be empty")
func TestRequestIDFromHeader(t *testing.T) {
headerName := "X-Request-ID"
headerValue := "71a75329-d9f9-4d25-957e-e689a7b68d78"
handler := Handler{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
value, _ := r.Context().Value(httpserver.RequestIDCtxKey).(string)
if value != headerValue {
t.Errorf("Request ID should be '%s' but got '%s'", headerValue, value)
}
return 0, nil
}),
HeaderName: headerName,
}
if value != reqid {
t.Fatal("Request ID does not match")
req, err := http.NewRequest("GET", "http://localhost/", nil)
if err != nil {
t.Fatal("Could not create HTTP request:", err)
}
req.Header.Set(headerName, headerValue)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
}
......@@ -27,14 +27,19 @@ func init() {
}
func setup(c *caddy.Controller) error {
var headerName string
for c.Next() {
if c.NextArg() {
return c.ArgErr() //no arg expected.
headerName = c.Val()
}
if c.NextArg() {
return c.ArgErr()
}
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return Handler{Next: next}
return Handler{Next: next, HeaderName: headerName}
})
return nil
......
......@@ -45,7 +45,15 @@ func TestSetup(t *testing.T) {
}
func TestSetupWithArg(t *testing.T) {
c := caddy.NewTestController("http", `requestid abc`)
c := caddy.NewTestController("http", `requestid X-Request-ID`)
err := setup(c)
if err != nil {
t.Errorf("Expected no error, got: %v", err)
}
}
func TestSetupWithTooManyArgs(t *testing.T) {
c := caddy.NewTestController("http", `requestid foo bar`)
err := setup(c)
if err == nil {
t.Errorf("Expected an error, got: %v", err)
......
......@@ -107,6 +107,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
if d.IsDir() {
// ensure there is a trailing slash
if urlCopy.Path[len(urlCopy.Path)-1] != '/' {
for strings.HasPrefix(urlCopy.Path, "//") {
// prevent path-based open redirects
urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/")
}
urlCopy.Path += "/"
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
return http.StatusMovedPermanently, nil
......@@ -131,6 +135,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
}
if redir {
for strings.HasPrefix(urlCopy.Path, "//") {
// prevent path-based open redirects
urlCopy.Path = strings.TrimPrefix(urlCopy.Path, "/")
}
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
return http.StatusMovedPermanently, nil
}
......
......@@ -77,9 +77,9 @@ func TestServeHTTP(t *testing.T) {
{
url: "https://foo/dirwithindex/",
expectedStatus: http.StatusOK,
expectedBodyContent: testFiles[webrootDirwithindexIndeHTML],
expectedBodyContent: testFiles[webrootDirwithindexIndexHTML],
expectedEtag: `"2n9cw"`,
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndeHTML])),
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndexHTML])),
},
// Test 4 - access folder with index file without trailing slash
{
......@@ -235,16 +235,38 @@ func TestServeHTTP(t *testing.T) {
expectedBodyContent: movedPermanently,
},
{
// Test 27 - Check etag
url: "https://foo/notindex.html",
expectedStatus: http.StatusOK,
expectedBodyContent: testFiles[webrootNotIndexHTML],
expectedEtag: `"2n9cm"`,
expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])),
},
{
// Test 28 - Prevent path-based open redirects (directory)
url: "https://foo//example.com%2f..",
expectedStatus: http.StatusMovedPermanently,
expectedLocation: "https://foo/example.com/../",
expectedBodyContent: movedPermanently,
},
{
// Test 29 - Prevent path-based open redirects (file)
url: "https://foo//example.com%2f../dirwithindex/index.html",
expectedStatus: http.StatusMovedPermanently,
expectedLocation: "https://foo/example.com/../dirwithindex/",
expectedBodyContent: movedPermanently,
},
{
// Test 29 - Prevent path-based open redirects (extra leading slashes)
url: "https://foo///example.com%2f..",
expectedStatus: http.StatusMovedPermanently,
expectedLocation: "https://foo/example.com/../",
expectedBodyContent: movedPermanently,
},
}
for i, test := range tests {
// set up response writer and rewuest
// set up response writer and request
responseRecorder := httptest.NewRecorder()
request, err := http.NewRequest("GET", test.url, nil)
if err != nil {
......@@ -518,7 +540,7 @@ var (
webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html")
webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html")
webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.html")
webrootDirwithindexIndeHTML = filepath.Join(webrootName, "dirwithindex", "index.html")
webrootDirwithindexIndexHTML = filepath.Join(webrootName, "dirwithindex", "index.html")
webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html")
webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz")
webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br")
......@@ -544,7 +566,7 @@ var testFiles = map[string]string{
webrootFile1HTML: "<h1>file1.html</h1>",
webrootNotIndexHTML: "<h1>notindex.html</h1>",
webrootDirFile2HTML: "<h1>dir/file2.html</h1>",
webrootDirwithindexIndeHTML: "<h1>dirwithindex/index.html</h1>",
webrootDirwithindexIndexHTML: "<h1>dirwithindex/index.html</h1>",
webrootDirHiddenHTML: "<h1>dir/hidden.html</h1>",
webrootSubGzippedHTML: "<h1>gzipped.html</h1>",
webrootSubGzippedHTMLGz: "1.gzipped.html.gz",
......
......@@ -62,100 +62,79 @@ func TestTemplates(t *testing.T) {
BufPool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }},
}
// Test tmpl on /photos/test.html
req, err := http.NewRequest("GET", "/photos/test.html", nil)
if err != nil {
t.Fatalf("Test: Could not create HTTP request: %v", err)
}
req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL))
rec := httptest.NewRecorder()
tmpl.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
}
// register custom function which is used in template
httpserver.TemplateFuncs["root"] = func() string { return "root" }
respBody := rec.Body.String()
expectedBody := `<!DOCTYPE html><html><head><title>test page</title></head><body><h1>Header title</h1>
for _, c := range []struct {
tpl Templates
req string
respCode int
res string
}{
{
tpl: tmpl,
req: "/photos/test.html",
respCode: http.StatusOK,
res: `<!DOCTYPE html><html><head><title>test page</title></head><body><h1>Header title</h1>
</body></html>
`
if respBody != expectedBody {
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
}
// Test tmpl on /images/img.htm
req, err = http.NewRequest("GET", "/images/img.htm", nil)
if err != nil {
t.Fatalf("Could not create HTTP request: %v", err)
}
req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL))
rec = httptest.NewRecorder()
tmpl.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
}
`,
},
respBody = rec.Body.String()
expectedBody = `<!DOCTYPE html><html><head><title>img</title></head><body><h1>Header title</h1>
{
tpl: tmpl,
req: "/images/img.htm",
respCode: http.StatusOK,
res: `<!DOCTYPE html><html><head><title>img</title></head><body><h1>Header title</h1>
</body></html>
`
if respBody != expectedBody {
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
}
// Test tmpl on /images/img2.htm
req, err = http.NewRequest("GET", "/images/img2.htm", nil)
if err != nil {
t.Fatalf("Could not create HTTP request: %v", err)
}
req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL))
rec = httptest.NewRecorder()
tmpl.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
}
respBody = rec.Body.String()
expectedBody = `<!DOCTYPE html><html><head><title>img</title></head><body>{{.Include "header.html"}}</body></html>
`
if respBody != expectedBody {
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
}
// Test tmplroot on /root.html
req, err = http.NewRequest("GET", "/root.html", nil)
if err != nil {
t.Fatalf("Could not create HTTP request: %v", err)
}
req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL))
rec = httptest.NewRecorder()
// register custom function which is used in template
httpserver.TemplateFuncs["root"] = func() string { return "root" }
tmplroot.ServeHTTP(rec, req)
`,
},
if rec.Code != http.StatusOK {
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
}
{
tpl: tmpl,
req: "/images/img2.htm",
respCode: http.StatusOK,
res: `<!DOCTYPE html><html><head><title>img</title></head><body>{{.Include "header.html"}}</body></html>
`,
},
respBody = rec.Body.String()
expectedBody = `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1>
{
tpl: tmplroot,
req: "/root.html",
respCode: http.StatusOK,
res: `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1>
</body></html>
`
`,
},
if respBody != expectedBody {
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
// test extension filter
{
tpl: tmplroot,
req: "/as_it_is.txt",
respCode: http.StatusOK,
res: `<!DOCTYPE html><html><head><title>as it is</title></head><body>{{.Include "header.html"}}</body></html>
`,
},
} {
c := c
t.Run("", func(t *testing.T) {
req, err := http.NewRequest("GET", c.req, nil)
if err != nil {
t.Fatalf("Test: Could not create HTTP request: %v", err)
}
req = req.WithContext(context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL))
rec := httptest.NewRecorder()
c.tpl.ServeHTTP(rec, req)
if rec.Code != c.respCode {
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, c.respCode)
}
respBody := rec.Body.String()
if respBody != c.res {
t.Fatalf("Test: the expected body %v is different from the response one: %v", c.res, respBody)
}
})
}
}
<!DOCTYPE html><html><head><title>as it is</title></head><body>{{.Include "header.html"}}</body></html>
......@@ -46,7 +46,7 @@ sudo useradd \
sudo mkdir /etc/caddy
sudo chown -R root:www-data /etc/caddy
sudo mkdir /etc/ssl/caddy
sudo chown -R www-data:root /etc/ssl/caddy
sudo chown -R root:www-data /etc/ssl/caddy
sudo chmod 0770 /etc/ssl/caddy
```
......@@ -91,6 +91,7 @@ Install the systemd service unit configuration file, reload the systemd daemon,
and start caddy:
```bash
wget https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service
sudo cp caddy.service /etc/systemd/system/
sudo chown root:root /etc/systemd/system/caddy.service
sudo chmod 644 /etc/systemd/system/caddy.service
......
......@@ -30,8 +30,8 @@ LimitNPROC=512
; Use private /tmp and /var/tmp, which are discarded after caddy stops.
PrivateTmp=true
; Use a minimal /dev
PrivateDevices=true
; Use a minimal /dev (May bring additional security if switched to 'true', but it may not work on Raspberry Pi's or other devices, so it has been disabled in this dist.)
PrivateDevices=false
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=true
; Make /usr, /boot, /etc and possibly some more folders read-only.
......@@ -41,7 +41,7 @@ ProtectSystem=full
ReadWriteDirectories=/etc/ssl/caddy
; The following additional security directives only work with systemd v229 or later.
; They further retrict privileges that can be gained by caddy. Uncomment if you like.
; They further restrict privileges that can be gained by caddy. Uncomment if you like.
; Note that you may have to add capabilities required by any plugins in use.
;CapabilityBoundingSet=CAP_NET_BIND_SERVICE
;AmbientCapabilities=CAP_NET_BIND_SERVICE
......
......@@ -9,3 +9,19 @@ Usage
* Ensure that the folder `/etc/caddy` exists and that the folder `/etc/ssl/caddy` is owned by `www-data`.
* Create a Caddyfile in `/etc/caddy/Caddyfile`
* Now you can use `service caddy start|stop|restart|reload|status` as `root`.
Init script manipulation
-----
The init script supports configuration via the following files:
* `/etc/default/caddy` ( Debian based https://www.debian.org/doc/manuals/debian-reference/ch03.en.html#_the_default_parameter_for_each_init_script )
* `/etc/sysconfig/caddy` ( CentOS based https://www.centos.org/docs/5/html/5.2/Deployment_Guide/s1-sysconfig-files.html )
The following variables can be changed:
* DAEMON: path to the caddy binary file (default: `/usr/local/bin/caddy`)
* DAEMONUSER: user used to run caddy (default: `www-data`)
* PIDFILE: path to the pidfile (default: `/var/run/$NAME.pid`)
* LOGFILE: path to the log file for caddy daemon (not for access logs) (default: `/var/log/$NAME.log`)
* CONFIGFILE: path to the caddy configuration file (default: `/etc/caddy/Caddyfile`)
* CADDYPATH: path for SSL certificates managed by caddy (default: `/etc/ssl/caddy`)
* ULIMIT: open files limit (default: `8192`)
......@@ -20,18 +20,30 @@ DAEMONUSER=www-data
PIDFILE=/var/run/$NAME.pid
LOGFILE=/var/log/$NAME.log
CONFIGFILE=/etc/caddy/Caddyfile
DAEMONOPTS="-agree=true -log=$LOGFILE -conf=$CONFIGFILE"
USERBIND="setcap cap_net_bind_service=+ep"
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
CADDYPATH=/etc/ssl/caddy
ULIMIT=8192
test -x $DAEMON || exit 0
# allow overwriting variables
# Debian based
[ -e "/etc/default/caddy" ] && . /etc/default/caddy
# CentOS based
[ -e "/etc/sysconfig/caddy" ] && . /etc/sysconfig/caddy
if [ -z "$DAEMONOPTS" ]; then
# daemon options
DAEMONOPTS="-agree=true -log=$LOGFILE -conf=$CONFIGFILE"
fi
# Set the CADDYPATH; Let's Encrypt certificates will be written to this directory.
export CADDYPATH=/etc/ssl/caddy
export CADDYPATH
# Set the ulimits
ulimit -n 8192
ulimit -n ${ULIMIT}
start() {
......
......@@ -19,6 +19,7 @@ import (
"log"
"net"
"sort"
"sync"
"github.com/mholt/caddy/caddyfile"
)
......@@ -38,7 +39,7 @@ var (
// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = make(map[string]EventHook)
eventHooks = sync.Map{}
// parsingCallbacks maps server type to map of directive
// to list of callback functions. These aren't really
......@@ -67,12 +68,15 @@ func DescribePlugins() string {
str += " " + defaultCaddyfileLoader.name + "\n"
}
if len(eventHooks) > 0 {
// List the event hook plugins
// List the event hook plugins
hooks := ""
eventHooks.Range(func(k, _ interface{}) bool {
hooks += " hook." + k.(string) + "\n"
return true
})
if hooks != "" {
str += "\nEvent hook plugins:\n"
for hookPlugin := range eventHooks {
str += " hook." + hookPlugin + "\n"
}
str += hooks
}
// Let's alphabetize the rest of these...
......@@ -248,23 +252,23 @@ func RegisterEventHook(name string, hook EventHook) {
if name == "" {
panic("event hook must have a name")
}
if _, dup := eventHooks[name]; dup {
_, dup := eventHooks.LoadOrStore(name, hook)
if dup {
panic("hook named " + name + " already registered")
}
eventHooks[name] = hook
}
// EmitEvent executes the different hooks passing the EventType as an
// argument. This is a blocking function. Hook developers should
// use 'go' keyword if they don't want to block Caddy.
func EmitEvent(event EventName, info interface{}) {
for name, hook := range eventHooks {
err := hook(event, info)
eventHooks.Range(func(k, v interface{}) bool {
err := v.(EventHook)(event, info)
if err != nil {
log.Printf("error on '%s' hook: %v", name, err)
log.Printf("error on '%s' hook: %v", k.(string), err)
}
}
return true
})
}
// ParsingCallback is a function that is called after
......
......@@ -31,19 +31,19 @@ func trapSignalsPosix() {
for sig := range sigchan {
switch sig {
case syscall.SIGTERM:
log.Println("[INFO] SIGTERM: Terminating process")
case syscall.SIGQUIT:
log.Println("[INFO] SIGQUIT: Quitting process immediately")
for _, f := range OnProcessExit {
f() // only perform important cleanup actions
}
os.Exit(0)
case syscall.SIGQUIT:
log.Println("[INFO] SIGQUIT: Shutting down")
exitCode := executeShutdownCallbacks("SIGQUIT")
case syscall.SIGTERM:
log.Println("[INFO] SIGTERM: Shutting down servers then terminating")
exitCode := executeShutdownCallbacks("SIGTERM")
err := Stop()
if err != nil {
log.Printf("[ERROR] SIGQUIT stop: %v", err)
log.Printf("[ERROR] SIGTERM stop: %v", err)
exitCode = 3
}
for _, f := range OnProcessExit {
......@@ -51,13 +51,6 @@ func trapSignalsPosix() {
}
os.Exit(exitCode)
case syscall.SIGHUP:
log.Println("[INFO] SIGHUP: Hanging up")
err := Stop()
if err != nil {
log.Printf("[ERROR] SIGHUP stop: %v", err)
}
case syscall.SIGUSR1:
log.Println("[INFO] SIGUSR1: Reloading")
......@@ -94,6 +87,9 @@ func trapSignalsPosix() {
if err := Upgrade(); err != nil {
log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
}
case syscall.SIGHUP:
// ignore; this signal is sometimes sent outside of the user's control
}
}
}()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment