mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-13 09:08:50 +01:00
Merge branch 'master' into cert-cache
# Conflicts: # sigtrap_posix.go
This commit is contained in:
commit
f26447e2fb
24 changed files with 286 additions and 165 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -16,4 +16,6 @@ Caddyfile
|
||||||
|
|
||||||
og_static/
|
og_static/
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
*.bat
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<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>
|
</p>
|
||||||
<h3 align="center">Every Site on HTTPS <!-- Serve Confidently --></h3>
|
<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>
|
<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)
|
return caddy.CaddyfileFromPipe(os.Stdin, serverType)
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(conf)
|
var contents []byte
|
||||||
if err != nil {
|
if strings.Contains(conf, "*") {
|
||||||
return nil, err
|
// 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{
|
return caddy.CaddyfileInput{
|
||||||
Contents: contents,
|
Contents: contents,
|
||||||
Filepath: conf,
|
Filepath: conf,
|
||||||
|
@ -221,6 +229,8 @@ func setVersion() {
|
||||||
// setCPU parses string cpu and sets GOMAXPROCS
|
// setCPU parses string cpu and sets GOMAXPROCS
|
||||||
// according to its value. It accepts either
|
// according to its value. It accepts either
|
||||||
// a number (e.g. 3) or a percent (e.g. 50%).
|
// 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 {
|
func setCPU(cpu string) error {
|
||||||
var numCPU int
|
var numCPU int
|
||||||
|
|
||||||
|
@ -236,6 +246,9 @@ func setCPU(cpu string) error {
|
||||||
}
|
}
|
||||||
percent = float32(pctInt) / 100
|
percent = float32(pctInt) / 100
|
||||||
numCPU = int(float32(availCPU) * percent)
|
numCPU = int(float32(availCPU) * percent)
|
||||||
|
if numCPU < 1 {
|
||||||
|
numCPU = 1
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Number
|
// Number
|
||||||
num, err := strconv.Atoi(cpu)
|
num, err := strconv.Atoi(cpu)
|
||||||
|
|
|
@ -41,6 +41,7 @@ func TestSetCPU(t *testing.T) {
|
||||||
{"invalid input", currentCPU, true},
|
{"invalid input", currentCPU, true},
|
||||||
{"invalid input%", currentCPU, true},
|
{"invalid input%", currentCPU, true},
|
||||||
{"9999", maxCPU, false}, // over available CPU
|
{"9999", maxCPU, false}, // over available CPU
|
||||||
|
{"1%", 1, false}, // under a single CPU; assume maxCPU < 100
|
||||||
} {
|
} {
|
||||||
err := setCPU(test.input)
|
err := setCPU(test.input)
|
||||||
if test.shouldErr && err == nil {
|
if test.shouldErr && err == nil {
|
||||||
|
|
|
@ -499,7 +499,7 @@ footer {
|
||||||
return;
|
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"));
|
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
|
||||||
timeList.forEach(localizeDatetime);
|
timeList.forEach(localizeDatetime);
|
||||||
|
|
|
@ -148,7 +148,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
|
||||||
case "HEAD":
|
case "HEAD":
|
||||||
resp, err = fcgiBackend.Head(env)
|
resp, err = fcgiBackend.Head(env)
|
||||||
case "GET":
|
case "GET":
|
||||||
resp, err = fcgiBackend.Get(env)
|
resp, err = fcgiBackend.Get(env, r.Body, contentLength)
|
||||||
case "OPTIONS":
|
case "OPTIONS":
|
||||||
resp, err = fcgiBackend.Options(env)
|
resp, err = fcgiBackend.Options(env)
|
||||||
default:
|
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.
|
// 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["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.
|
// 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)
|
resp, err = fcgi.PostForm(fcgiParams, values)
|
||||||
} else {
|
} else {
|
||||||
resp, err = fcgi.Get(fcgiParams)
|
rd := bytes.NewReader(data)
|
||||||
|
resp, err = fcgi.Get(fcgiParams, rd, int64(rd.Len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -115,6 +115,7 @@ type ResponseBuffer struct {
|
||||||
shouldBuffer func(status int, header http.Header) bool
|
shouldBuffer func(status int, header http.Header) bool
|
||||||
stream bool
|
stream bool
|
||||||
rw http.ResponseWriter
|
rw http.ResponseWriter
|
||||||
|
wroteHeader bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResponseBuffer returns a new ResponseBuffer that will
|
// 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
|
// upcoming body should be buffered, and then writes
|
||||||
// the header to the response.
|
// the header to the response.
|
||||||
func (rb *ResponseBuffer) WriteHeader(status int) {
|
func (rb *ResponseBuffer) WriteHeader(status int) {
|
||||||
|
if rb.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rb.wroteHeader = true
|
||||||
|
|
||||||
rb.status = status
|
rb.status = status
|
||||||
rb.stream = !rb.shouldBuffer(status, rb.header)
|
rb.stream = !rb.shouldBuffer(status, rb.header)
|
||||||
if rb.stream {
|
if rb.stream {
|
||||||
|
@ -163,6 +169,10 @@ func (rb *ResponseBuffer) WriteHeader(status int) {
|
||||||
// Write writes buf to rb.Buffer if buffering, otherwise
|
// Write writes buf to rb.Buffer if buffering, otherwise
|
||||||
// to the ResponseWriter directly if streaming.
|
// to the ResponseWriter directly if streaming.
|
||||||
func (rb *ResponseBuffer) Write(buf []byte) (int, error) {
|
func (rb *ResponseBuffer) Write(buf []byte) (int, error) {
|
||||||
|
if !rb.wroteHeader {
|
||||||
|
rb.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
if rb.stream {
|
if rb.stream {
|
||||||
return rb.ResponseWriterWrapper.Write(buf)
|
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
|
// from ~8,200 to ~9,600 on templated files by ensuring that this type
|
||||||
// implements io.ReaderFrom.
|
// implements io.ReaderFrom.
|
||||||
func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) {
|
func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) {
|
||||||
|
if !rb.wroteHeader {
|
||||||
|
rb.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
if rb.stream {
|
if rb.stream {
|
||||||
// first see if we can avoid any allocations at all
|
// first see if we can avoid any allocations at all
|
||||||
if wt, ok := src.(io.WriterTo); ok {
|
if wt, ok := src.(io.WriterTo); ok {
|
||||||
|
|
|
@ -240,6 +240,7 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
|
||||||
rp.Transport = &h2quic.RoundTripper{
|
rp.Transport = &h2quic.RoundTripper{
|
||||||
QuicConfig: &quic.Config{
|
QuicConfig: &quic.Config{
|
||||||
HandshakeTimeout: defaultCryptoHandshakeTimeout,
|
HandshakeTimeout: defaultCryptoHandshakeTimeout,
|
||||||
|
KeepAlive: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else if keepalive != http.DefaultMaxIdleConnsPerHost || strings.HasPrefix(target.Scheme, "srv") {
|
} else if keepalive != http.DefaultMaxIdleConnsPerHost || strings.HasPrefix(target.Scheme, "srv") {
|
||||||
|
|
|
@ -16,6 +16,7 @@ package requestid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -24,12 +25,29 @@ import (
|
||||||
|
|
||||||
// Handler is a middleware handler
|
// Handler is a middleware handler
|
||||||
type Handler struct {
|
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) {
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
reqid := uuid.New().String()
|
var reqid uuid.UUID
|
||||||
c := context.WithValue(r.Context(), httpserver.RequestIDCtxKey, reqid)
|
|
||||||
|
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)
|
r = r.WithContext(c)
|
||||||
|
|
||||||
return h.Next.ServeHTTP(w, r)
|
return h.Next.ServeHTTP(w, r)
|
||||||
|
|
|
@ -15,34 +15,53 @@
|
||||||
package requestid
|
package requestid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestID(t *testing.T) {
|
func TestRequestIDHandler(t *testing.T) {
|
||||||
request, err := http.NewRequest("GET", "http://localhost/", nil)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Could not create HTTP request:", err)
|
t.Fatal("Could not create HTTP request:", err)
|
||||||
}
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
reqid := uuid.New().String()
|
handler.ServeHTTP(rec, req)
|
||||||
|
}
|
||||||
c := context.WithValue(request.Context(), httpserver.RequestIDCtxKey, reqid)
|
|
||||||
|
func TestRequestIDFromHeader(t *testing.T) {
|
||||||
request = request.WithContext(c)
|
headerName := "X-Request-ID"
|
||||||
|
headerValue := "71a75329-d9f9-4d25-957e-e689a7b68d78"
|
||||||
// See caddyhttp/replacer.go
|
handler := Handler{
|
||||||
value, _ := request.Context().Value(httpserver.RequestIDCtxKey).(string)
|
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
value, _ := r.Context().Value(httpserver.RequestIDCtxKey).(string)
|
||||||
if value == "" {
|
if value != headerValue {
|
||||||
t.Fatal("Request ID should not be empty")
|
t.Errorf("Request ID should be '%s' but got '%s'", headerValue, value)
|
||||||
}
|
}
|
||||||
|
return 0, nil
|
||||||
if value != reqid {
|
}),
|
||||||
t.Fatal("Request ID does not match")
|
HeaderName: headerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func setup(c *caddy.Controller) error {
|
||||||
|
var headerName string
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
if c.NextArg() {
|
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 {
|
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||||
return Handler{Next: next}
|
return Handler{Next: next, HeaderName: headerName}
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -45,7 +45,15 @@ func TestSetup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetupWithArg(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)
|
err := setup(c)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected an error, got: %v", err)
|
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() {
|
if d.IsDir() {
|
||||||
// ensure there is a trailing slash
|
// ensure there is a trailing slash
|
||||||
if urlCopy.Path[len(urlCopy.Path)-1] != '/' {
|
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 += "/"
|
urlCopy.Path += "/"
|
||||||
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
||||||
return http.StatusMovedPermanently, nil
|
return http.StatusMovedPermanently, nil
|
||||||
|
@ -131,6 +135,10 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if redir {
|
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)
|
http.Redirect(w, r, urlCopy.String(), http.StatusMovedPermanently)
|
||||||
return http.StatusMovedPermanently, nil
|
return http.StatusMovedPermanently, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,9 +77,9 @@ func TestServeHTTP(t *testing.T) {
|
||||||
{
|
{
|
||||||
url: "https://foo/dirwithindex/",
|
url: "https://foo/dirwithindex/",
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBodyContent: testFiles[webrootDirwithindexIndeHTML],
|
expectedBodyContent: testFiles[webrootDirwithindexIndexHTML],
|
||||||
expectedEtag: `"2n9cw"`,
|
expectedEtag: `"2n9cw"`,
|
||||||
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndeHTML])),
|
expectedContentLength: strconv.Itoa(len(testFiles[webrootDirwithindexIndexHTML])),
|
||||||
},
|
},
|
||||||
// Test 4 - access folder with index file without trailing slash
|
// Test 4 - access folder with index file without trailing slash
|
||||||
{
|
{
|
||||||
|
@ -235,16 +235,38 @@ func TestServeHTTP(t *testing.T) {
|
||||||
expectedBodyContent: movedPermanently,
|
expectedBodyContent: movedPermanently,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Test 27 - Check etag
|
||||||
url: "https://foo/notindex.html",
|
url: "https://foo/notindex.html",
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedBodyContent: testFiles[webrootNotIndexHTML],
|
expectedBodyContent: testFiles[webrootNotIndexHTML],
|
||||||
expectedEtag: `"2n9cm"`,
|
expectedEtag: `"2n9cm"`,
|
||||||
expectedContentLength: strconv.Itoa(len(testFiles[webrootNotIndexHTML])),
|
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 {
|
for i, test := range tests {
|
||||||
// set up response writer and rewuest
|
// set up response writer and request
|
||||||
responseRecorder := httptest.NewRecorder()
|
responseRecorder := httptest.NewRecorder()
|
||||||
request, err := http.NewRequest("GET", test.url, nil)
|
request, err := http.NewRequest("GET", test.url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -518,7 +540,7 @@ var (
|
||||||
webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html")
|
webrootNotIndexHTML = filepath.Join(webrootName, "notindex.html")
|
||||||
webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html")
|
webrootDirFile2HTML = filepath.Join(webrootName, "dir", "file2.html")
|
||||||
webrootDirHiddenHTML = filepath.Join(webrootName, "dir", "hidden.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")
|
webrootSubGzippedHTML = filepath.Join(webrootName, "sub", "gzipped.html")
|
||||||
webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz")
|
webrootSubGzippedHTMLGz = filepath.Join(webrootName, "sub", "gzipped.html.gz")
|
||||||
webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br")
|
webrootSubGzippedHTMLBr = filepath.Join(webrootName, "sub", "gzipped.html.br")
|
||||||
|
@ -544,7 +566,7 @@ var testFiles = map[string]string{
|
||||||
webrootFile1HTML: "<h1>file1.html</h1>",
|
webrootFile1HTML: "<h1>file1.html</h1>",
|
||||||
webrootNotIndexHTML: "<h1>notindex.html</h1>",
|
webrootNotIndexHTML: "<h1>notindex.html</h1>",
|
||||||
webrootDirFile2HTML: "<h1>dir/file2.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>",
|
webrootDirHiddenHTML: "<h1>dir/hidden.html</h1>",
|
||||||
webrootSubGzippedHTML: "<h1>gzipped.html</h1>",
|
webrootSubGzippedHTML: "<h1>gzipped.html</h1>",
|
||||||
webrootSubGzippedHTMLGz: "1.gzipped.html.gz",
|
webrootSubGzippedHTMLGz: "1.gzipped.html.gz",
|
||||||
|
|
|
@ -62,100 +62,79 @@ func TestTemplates(t *testing.T) {
|
||||||
BufPool: &sync.Pool{New: func() interface{} { return new(bytes.Buffer) }},
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody := rec.Body.String()
|
|
||||||
expectedBody := `<!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>
|
|
||||||
</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
|
// register custom function which is used in template
|
||||||
httpserver.TemplateFuncs["root"] = func() string { return "root" }
|
httpserver.TemplateFuncs["root"] = func() string { return "root" }
|
||||||
tmplroot.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
for _, c := range []struct {
|
||||||
t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK)
|
tpl Templates
|
||||||
}
|
req string
|
||||||
|
respCode int
|
||||||
respBody = rec.Body.String()
|
res string
|
||||||
expectedBody = `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1>
|
}{
|
||||||
|
{
|
||||||
|
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>
|
</body></html>
|
||||||
`
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
if respBody != expectedBody {
|
{
|
||||||
t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody)
|
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>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
tpl: tmpl,
|
||||||
|
req: "/images/img2.htm",
|
||||||
|
respCode: http.StatusOK,
|
||||||
|
res: `<!DOCTYPE html><html><head><title>img</title></head><body>{{.Include "header.html"}}</body></html>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
tpl: tmplroot,
|
||||||
|
req: "/root.html",
|
||||||
|
respCode: http.StatusOK,
|
||||||
|
res: `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1>
|
||||||
|
</body></html>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
caddyhttp/templates/testdata/as_it_is.txt
vendored
Normal file
1
caddyhttp/templates/testdata/as_it_is.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<!DOCTYPE html><html><head><title>as it is</title></head><body>{{.Include "header.html"}}</body></html>
|
3
dist/init/linux-systemd/README.md
vendored
3
dist/init/linux-systemd/README.md
vendored
|
@ -46,7 +46,7 @@ sudo useradd \
|
||||||
sudo mkdir /etc/caddy
|
sudo mkdir /etc/caddy
|
||||||
sudo chown -R root:www-data /etc/caddy
|
sudo chown -R root:www-data /etc/caddy
|
||||||
sudo mkdir /etc/ssl/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
|
sudo chmod 0770 /etc/ssl/caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ Install the systemd service unit configuration file, reload the systemd daemon,
|
||||||
and start caddy:
|
and start caddy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
wget https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service
|
||||||
sudo cp caddy.service /etc/systemd/system/
|
sudo cp caddy.service /etc/systemd/system/
|
||||||
sudo chown root:root /etc/systemd/system/caddy.service
|
sudo chown root:root /etc/systemd/system/caddy.service
|
||||||
sudo chmod 644 /etc/systemd/system/caddy.service
|
sudo chmod 644 /etc/systemd/system/caddy.service
|
||||||
|
|
6
dist/init/linux-systemd/caddy.service
vendored
6
dist/init/linux-systemd/caddy.service
vendored
|
@ -30,8 +30,8 @@ LimitNPROC=512
|
||||||
|
|
||||||
; Use private /tmp and /var/tmp, which are discarded after caddy stops.
|
; Use private /tmp and /var/tmp, which are discarded after caddy stops.
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
; Use a minimal /dev
|
; 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=true
|
PrivateDevices=false
|
||||||
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
|
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
; Make /usr, /boot, /etc and possibly some more folders read-only.
|
; Make /usr, /boot, /etc and possibly some more folders read-only.
|
||||||
|
@ -41,7 +41,7 @@ ProtectSystem=full
|
||||||
ReadWriteDirectories=/etc/ssl/caddy
|
ReadWriteDirectories=/etc/ssl/caddy
|
||||||
|
|
||||||
; The following additional security directives only work with systemd v229 or later.
|
; 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.
|
; Note that you may have to add capabilities required by any plugins in use.
|
||||||
;CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
;CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
;AmbientCapabilities=CAP_NET_BIND_SERVICE
|
;AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
|
16
dist/init/linux-sysvinit/README.md
vendored
16
dist/init/linux-sysvinit/README.md
vendored
|
@ -9,3 +9,19 @@ Usage
|
||||||
* Ensure that the folder `/etc/caddy` exists and that the folder `/etc/ssl/caddy` is owned by `www-data`.
|
* 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`
|
* Create a Caddyfile in `/etc/caddy/Caddyfile`
|
||||||
* Now you can use `service caddy start|stop|restart|reload|status` as `root`.
|
* 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`)
|
||||||
|
|
18
dist/init/linux-sysvinit/caddy
vendored
18
dist/init/linux-sysvinit/caddy
vendored
|
@ -20,18 +20,30 @@ DAEMONUSER=www-data
|
||||||
PIDFILE=/var/run/$NAME.pid
|
PIDFILE=/var/run/$NAME.pid
|
||||||
LOGFILE=/var/log/$NAME.log
|
LOGFILE=/var/log/$NAME.log
|
||||||
CONFIGFILE=/etc/caddy/Caddyfile
|
CONFIGFILE=/etc/caddy/Caddyfile
|
||||||
DAEMONOPTS="-agree=true -log=$LOGFILE -conf=$CONFIGFILE"
|
|
||||||
|
|
||||||
USERBIND="setcap cap_net_bind_service=+ep"
|
USERBIND="setcap cap_net_bind_service=+ep"
|
||||||
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
|
STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
|
||||||
|
CADDYPATH=/etc/ssl/caddy
|
||||||
|
ULIMIT=8192
|
||||||
|
|
||||||
test -x $DAEMON || exit 0
|
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.
|
# Set the CADDYPATH; Let's Encrypt certificates will be written to this directory.
|
||||||
export CADDYPATH=/etc/ssl/caddy
|
export CADDYPATH
|
||||||
|
|
||||||
# Set the ulimits
|
# Set the ulimits
|
||||||
ulimit -n 8192
|
ulimit -n ${ULIMIT}
|
||||||
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
|
30
plugins.go
30
plugins.go
|
@ -19,6 +19,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/mholt/caddy/caddyfile"
|
"github.com/mholt/caddy/caddyfile"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +39,7 @@ var (
|
||||||
|
|
||||||
// eventHooks is a map of hook name to Hook. All hooks plugins
|
// eventHooks is a map of hook name to Hook. All hooks plugins
|
||||||
// must have a name.
|
// must have a name.
|
||||||
eventHooks = make(map[string]EventHook)
|
eventHooks = sync.Map{}
|
||||||
|
|
||||||
// parsingCallbacks maps server type to map of directive
|
// parsingCallbacks maps server type to map of directive
|
||||||
// to list of callback functions. These aren't really
|
// to list of callback functions. These aren't really
|
||||||
|
@ -67,12 +68,15 @@ func DescribePlugins() string {
|
||||||
str += " " + defaultCaddyfileLoader.name + "\n"
|
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"
|
str += "\nEvent hook plugins:\n"
|
||||||
for hookPlugin := range eventHooks {
|
str += hooks
|
||||||
str += " hook." + hookPlugin + "\n"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's alphabetize the rest of these...
|
// Let's alphabetize the rest of these...
|
||||||
|
@ -248,23 +252,23 @@ func RegisterEventHook(name string, hook EventHook) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
panic("event hook must have a 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")
|
panic("hook named " + name + " already registered")
|
||||||
}
|
}
|
||||||
eventHooks[name] = hook
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmitEvent executes the different hooks passing the EventType as an
|
// EmitEvent executes the different hooks passing the EventType as an
|
||||||
// argument. This is a blocking function. Hook developers should
|
// argument. This is a blocking function. Hook developers should
|
||||||
// use 'go' keyword if they don't want to block Caddy.
|
// use 'go' keyword if they don't want to block Caddy.
|
||||||
func EmitEvent(event EventName, info interface{}) {
|
func EmitEvent(event EventName, info interface{}) {
|
||||||
for name, hook := range eventHooks {
|
eventHooks.Range(func(k, v interface{}) bool {
|
||||||
err := hook(event, info)
|
err := v.(EventHook)(event, info)
|
||||||
|
|
||||||
if err != nil {
|
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
|
// ParsingCallback is a function that is called after
|
||||||
|
|
|
@ -31,19 +31,19 @@ func trapSignalsPosix() {
|
||||||
|
|
||||||
for sig := range sigchan {
|
for sig := range sigchan {
|
||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGTERM:
|
case syscall.SIGQUIT:
|
||||||
log.Println("[INFO] SIGTERM: Terminating process")
|
log.Println("[INFO] SIGQUIT: Quitting process immediately")
|
||||||
for _, f := range OnProcessExit {
|
for _, f := range OnProcessExit {
|
||||||
f() // only perform important cleanup actions
|
f() // only perform important cleanup actions
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|
||||||
case syscall.SIGQUIT:
|
case syscall.SIGTERM:
|
||||||
log.Println("[INFO] SIGQUIT: Shutting down")
|
log.Println("[INFO] SIGTERM: Shutting down servers then terminating")
|
||||||
exitCode := executeShutdownCallbacks("SIGQUIT")
|
exitCode := executeShutdownCallbacks("SIGTERM")
|
||||||
err := Stop()
|
err := Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] SIGQUIT stop: %v", err)
|
log.Printf("[ERROR] SIGTERM stop: %v", err)
|
||||||
exitCode = 3
|
exitCode = 3
|
||||||
}
|
}
|
||||||
for _, f := range OnProcessExit {
|
for _, f := range OnProcessExit {
|
||||||
|
@ -51,13 +51,6 @@ func trapSignalsPosix() {
|
||||||
}
|
}
|
||||||
os.Exit(exitCode)
|
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:
|
case syscall.SIGUSR1:
|
||||||
log.Println("[INFO] SIGUSR1: Reloading")
|
log.Println("[INFO] SIGUSR1: Reloading")
|
||||||
|
|
||||||
|
@ -94,6 +87,9 @@ func trapSignalsPosix() {
|
||||||
if err := Upgrade(); err != nil {
|
if err := Upgrade(); err != nil {
|
||||||
log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
|
log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case syscall.SIGHUP:
|
||||||
|
// ignore; this signal is sometimes sent outside of the user's control
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Reference in a new issue