package middleware import ( "net/http" "runtime/debug" ) // panicBody is the entire response a recovered panic produces. No // template, no detail: the client learns that the request failed, and // everything about why goes to the log and to Sentry, where it is not // attacker-readable. const panicBody = "internal server error" // Recoverer turns a panicking handler into a 500 rather than a dropped // connection. // // net/http already recovers panics, but what it does is close the // connection without a response, so the client sees a transport error // and no status. Answering 500 is the difference between "the service // is broken" and "the network is broken" for everyone downstream. // // A panic after the response has started cannot be turned into a 500 — // the status is already on the wire — so in that case the connection is // deliberately dropped by re-panicking to net/http, which is the only // honest signal left that the body is truncated. A truncated 200 that // looks complete is worse than a broken connection. func (m *Middleware) Recoverer() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rec := newResponseRecorder(w) defer func() { v := recover() if v == nil { return } // http.ErrAbortHandler is net/http's documented way for // a handler to abandon a response on purpose. It is not // a bug, so it is not reported; it is re-raised for // net/http to handle as it always does. //nolint:errorlint,err113 // a sentinel value, compared as net/http documents. if v == http.ErrAbortHandler { panic(v) } m.params.Sentry.CapturePanic(v, debug.Stack()) if rec.Written() { panic(v) } http.Error(rec, panicBody, http.StatusInternalServerError) }() next.ServeHTTP(rec, r) }) } }