mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 02:09:47 +08:00
15fa5cf2da
OnStartup and OnShutdown callbacks now run as part of restarts, too. The startup and shutdown directives only run their commands NOT as part of restarts, as before. Some middleware that use OnStartup may need to switch to OnFirstStartup and implement OnFinalShutdown to do any cleanup as needed.
142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
// Package errors implements an HTTP error handling middleware.
|
|
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mholt/caddy"
|
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
|
)
|
|
|
|
func init() {
|
|
caddy.RegisterPlugin("errors", caddy.Plugin{
|
|
ServerType: "http",
|
|
Action: setup,
|
|
})
|
|
}
|
|
|
|
// ErrorHandler handles HTTP errors (and errors from other middleware).
|
|
type ErrorHandler struct {
|
|
Next httpserver.Handler
|
|
ErrorPages map[int]string // map of status code to filename
|
|
LogFile string
|
|
Log *log.Logger
|
|
LogRoller *httpserver.LogRoller
|
|
Debug bool // if true, errors are written out to client rather than to a log
|
|
file *os.File // a log file to close when done
|
|
}
|
|
|
|
func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
|
defer h.recovery(w, r)
|
|
|
|
status, err := h.Next.ServeHTTP(w, r)
|
|
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
|
|
if h.Debug {
|
|
// Write error to response instead of to log
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
fmt.Fprintln(w, errMsg)
|
|
return 0, err // returning 0 signals that a response has been written
|
|
}
|
|
h.Log.Println(errMsg)
|
|
}
|
|
|
|
if status >= 400 {
|
|
h.errorPage(w, r, status)
|
|
return 0, err
|
|
}
|
|
|
|
return status, err
|
|
}
|
|
|
|
// errorPage serves a static error page to w according to the status
|
|
// code. If there is an error serving the error page, a plaintext error
|
|
// message is written instead, and the extra error is logged.
|
|
func (h ErrorHandler) errorPage(w http.ResponseWriter, r *http.Request, code int) {
|
|
// See if an error page for this status code was specified
|
|
if pagePath, ok := h.ErrorPages[code]; ok {
|
|
// Try to open it
|
|
errorPage, err := os.Open(pagePath)
|
|
if err != nil {
|
|
// An additional error handling an error... <insert grumpy cat here>
|
|
h.Log.Printf("%s [NOTICE %d %s] could not load error page: %v",
|
|
time.Now().Format(timeFormat), code, r.URL.String(), err)
|
|
httpserver.DefaultErrorFunc(w, r, code)
|
|
return
|
|
}
|
|
defer errorPage.Close()
|
|
|
|
// Copy the page body into the response
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(code)
|
|
_, err = io.Copy(w, errorPage)
|
|
|
|
if err != nil {
|
|
// Epic fail... sigh.
|
|
h.Log.Printf("%s [NOTICE %d %s] could not respond with %s: %v",
|
|
time.Now().Format(timeFormat), code, r.URL.String(), pagePath, err)
|
|
httpserver.DefaultErrorFunc(w, r, code)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Default error response
|
|
httpserver.DefaultErrorFunc(w, r, code)
|
|
}
|
|
|
|
func (h ErrorHandler) recovery(w http.ResponseWriter, r *http.Request) {
|
|
rec := recover()
|
|
if rec == nil {
|
|
return
|
|
}
|
|
|
|
// Obtain source of panic
|
|
// From: https://gist.github.com/swdunlop/9629168
|
|
var name, file string // function name, file name
|
|
var line int
|
|
var pc [16]uintptr
|
|
n := runtime.Callers(3, pc[:])
|
|
for _, pc := range pc[:n] {
|
|
fn := runtime.FuncForPC(pc)
|
|
if fn == nil {
|
|
continue
|
|
}
|
|
file, line = fn.FileLine(pc)
|
|
name = fn.Name()
|
|
if !strings.HasPrefix(name, "runtime.") {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Trim file path
|
|
delim := "/caddy/"
|
|
pkgPathPos := strings.Index(file, delim)
|
|
if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) {
|
|
file = file[pkgPathPos+len(delim):]
|
|
}
|
|
|
|
panicMsg := fmt.Sprintf("%s [PANIC %s] %s:%d - %v", time.Now().Format(timeFormat), r.URL.String(), file, line, rec)
|
|
if h.Debug {
|
|
// Write error and stack trace to the response rather than to a log
|
|
var stackBuf [4096]byte
|
|
stack := stackBuf[:runtime.Stack(stackBuf[:], false)]
|
|
httpserver.WriteTextResponse(w, http.StatusInternalServerError, fmt.Sprintf("%s\n\n%s", panicMsg, stack))
|
|
} else {
|
|
// Currently we don't use the function name, since file:line is more conventional
|
|
h.Log.Printf(panicMsg)
|
|
h.errorPage(w, r, http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
const timeFormat = "02/Jan/2006:15:04:05 -0700"
|