diff --git a/recovery.go b/recovery.go new file mode 100644 index 0000000..9debe98 --- /dev/null +++ b/recovery.go @@ -0,0 +1,69 @@ +package narco + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "runtime" + + "github.com/codemodus/chain" + "golang.org/x/net/context" +) + +type StackResponseFormatter func(w io.Writer, err interface{}, stack []byte) + +func GoFormatStack(w io.Writer, err interface{}, stack []byte) { + fmt.Fprintf(w, "Server internal error: %s\n%s", err, stack) +} + +func HtmlFormatStack(w io.Writer, err interface{}, stack []byte) { + fmt.Fprintf(w, ` + +

Server internal error: %s

+
%s
+ +`, err, stack) +} + +type Recoverer struct { + Logger *log.Logger + ShowStackInResponse StackResponseFormatter + StackOtherGoroutine bool +} + +func NewRecoverer() *Recoverer { + return &Recoverer{ + Logger: log.New(os.Stdout, "[narco] ", log.LstdFlags), + ShowStackInResponse: nil, + StackOtherGoroutine: false, + } +} + +func (rec *Recoverer) Wrap() func(chain.Handler) chain.Handler { + return func(other chain.Handler) chain.Handler { + return chain.HandlerFunc(func(ctx context.Context, h http.ResponseWriter, req *http.Request) { + defer func() { + err := recover() + if err == nil { + return + } + // h.Header()["Content-Type"] = []string{"text/html; charset=utf-8"} + h.WriteHeader(http.StatusInternalServerError) + + stack := make([]byte, 8*1024) + stack = stack[:runtime.Stack(stack, rec.StackOtherGoroutine)] + rec.Logger.Printf("PANIC: %s\n%s", err, stack) + + if rec.ShowStackInResponse != nil { + rec.ShowStackInResponse(h, err, stack) + } else { + fmt.Fprintf(h, "%s\n", http.StatusText(http.StatusInternalServerError)) + } + }() + + other.ServeHTTPContext(ctx, h, req) + }) + } +}