Go

How does net/http handle a panic in a handler? What are the pitfalls?

Question 323HardGo 1.22 to 1.25

http.Server recovers panics in each connection's goroutine. It logs the panic with a stack trace to ErrorLog and closes the connection, so the client gets a broken response and the rest of the server keeps running. Panicking with http.ErrAbortHandler aborts the response without logging a stack trace.

Pitfalls: (1) goroutines spawned by the handler are not protected, and a panic there kills the process. (2) The client gets no proper 500. (3) State the handler modified, such as locks held without defer Unlock or half-written caches, may be left inconsistent. Many teams add their own middleware so they control the response and the logging:

func Recoverer(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			rec := recover()
			if rec == nil {
				return
			}
			if rec == http.ErrAbortHandler {
				panic(rec) // preserve abort semantics
			}
			slog.Error("panic", "err", rec, "path", r.URL.Path,
				"stack", string(debug.Stack()))
			http.Error(w, http.StatusText(http.StatusInternalServerError),
				http.StatusInternalServerError)
		}()
		next.ServeHTTP(w, r)
	})
}

If the handler already wrote headers, http.Error can't change the status code. gRPC does not recover handler panics by default, so use a recovery interceptor.

More on Error Handling & panics

All 37 Error Handling & panics questions