What does http.ErrAbortHandler do when a Go http.Handler panics with it?
answer
- a panic value with a meaning
- the abort is not the special part
- the error log stays quiet
- proxies use it when clients hang up
- middleware must re-panic on it
basics
~20 shttp.ErrAbortHandler is a sentinel panic value meaning stop this response deliberately. Panicking with it aborts the response like any panic, but net/http suppresses the stack-trace log line, so it is the quiet way to give up on a request.
solid answer
~50 s`http.ErrAbortHandler` is a sentinel error value in `net/http` that exists to be panicked with. Any panic out of `ServeHTTP` aborts the response and closes the connection, but panicking with this particular value additionally tells the server not to log the panic and stack trace — the abort was intentional, so the noise would be meaningless. The standard reverse proxy in `net/http/httputil` uses it when copying an upstream response to the client fails, which typically means the client hung up mid-body. The practical consequence for a recovery middleware is that it must compare the recovered value against `http.ErrAbortHandler` and re-panic when it matches, instead of converting it into a 500. Swallowing it turns a deliberate silent abort into a fabricated status code on a connection the peer may already have closed, plus a spurious panic alert.
code
go · 11 linesdefer func() {
rec := recover()
if rec == nil {
return
}
if rec == http.ErrAbortHandler {
panic(rec) // deliberate abort: let the server drop it silently
}
recordPanic(r, rec, debug.Stack())
w.WriteHeader(http.StatusInternalServerError)
}()go deeper
It is enough to know that net/http defines a special panic value meaning abort this response on purpose, and that it exists to keep the error log quiet.
Be able to say exactly what is suppressed — the stack-trace log line, not the abort — and to write the re-panic branch a recovery wrapper needs.
Connect it to real traffic: clients disconnecting mid-body through a proxy would otherwise fill the error log and your panic metric with non-incidents.
Decide what your platform's recoverer treats as an incident against normal traffic, and make sure alert thresholds are not driven by client disconnects.
## A panic value with a meaning Most panic values are accidents — a nil map assignment, a bad index, a failed type assertion. `http.ErrAbortHandler` is the opposite: a sentinel value defined by `net/http` for the express purpose of being passed to `panic()`. Its documentation says it is a sentinel panic value used to abort a handler, and that while *any* panic from `ServeHTTP` aborts the response to the client, panicking with `ErrAbortHandler` also suppresses the logging of a stack trace to the server's error log. So two things happen, and only one of them is special: - **The response is aborted.** Same as any panic: nothing further is written, and the connection is closed. Not special. - **Nothing is logged.** The server's connection-level recover explicitly compares the recovered value to `ErrAbortHandler` and skips its `http: panic serving ...` line when they match. That is the whole feature. ## Why the standard library needs it The motivating case is a proxy. `net/http/httputil`'s reverse proxy streams an upstream response to the client. If that copy fails partway — most often because the client disconnected while the body was in flight — there is no honest way to finish the response: the status and headers are already sent, so the proxy cannot switch to a 502. The only correct move is to abandon the response so the client sees a truncated transfer rather than a plausible-looking short body. It does that by panicking with `ErrAbortHandler`. Logging a full stack trace every time a browser cancels a download would flood the error log with entries that describe no bug at all, hence the suppression. The same reasoning applies to your own code any time you must abandon a response you have already committed to, and the caller cannot be told anything useful. ## The interaction with recovery middleware This is where the interview question actually lands. A recovery middleware that treats every non-nil `recover()` as a bug does the wrong thing here: - It stops the abort. The handler returns normally, so the server completes the response — the client gets whatever bytes were already written plus a clean end, which is precisely the misleading outcome the abort was meant to prevent. - It writes a status that may not even be legal at that point, and if headers were already sent the server logs a superfluous `WriteHeader` complaint. - It fires your panic metric and possibly a page, for an event that is normal traffic. The fix is one comparison. Recover, check for the sentinel, and re-panic when it matches so the connection layer performs its silent abort: ```go if rec := recover(); rec != nil { if rec == http.ErrAbortHandler { panic(rec) } // ordinary bug: log, count, respond } ``` Re-panicking inside a deferred function is legal and continues unwinding with the new panic value, which is why this works. ## Comparison, not wrapping `ErrAbortHandler` is an `error` created with `errors.New`, so identity comparison with `==` is the natural check, and it is what the server itself does. If your code deliberately wraps it, use `errors.Is` — but wrapping a sentinel that is *only* meaningful by identity to the runtime path that checks it with `==` is a good way to lose the suppression, so prefer to panic with the bare value. ## Where it should not be used It is not a general "return early" mechanism. If you can still write a status, write one — `ErrAbortHandler` deliberately throws away the connection and gives the client no explanation. Reach for it only when the response is already partly committed and continuing would produce output the client could mistake for a complete answer. ## Summary A rarely-known but genuinely useful corner: it is the difference between "I crashed" and "I am cutting this off on purpose, do not page anyone". Knowing it also proves you have read what a recovery middleware needs to special-case.
- What goes wrong if a recovery middleware converts that sentinel into a 500 like any other panic?The abort is cancelled: the handler returns normally, the server finishes the response, and a client that should have seen a truncated transfer instead gets a clean end to a partial body. If headers were already sent, the extra WriteHeader is ignored with a superfluous-write log line, and your panic counter records an incident that never was.
- When would you panic with it in your own handler rather than returning an error?Only when you have already committed part of the response and cannot honestly finish it — for example you streamed half a large export and then the source failed. Switching to a 5xx is impossible because the status is on the wire, so aborting the connection is the only way to signal the truncation. If nothing has been written yet, write a status instead.
saying these in an interview costs you the question
- Thinks it is the only panic value that aborts a response
- Treats it as a general early-return from a handler
- Swallows it in recovery middleware like any other panic
- Believes it makes the server send a specific status code
- Assumes it prevents the connection from being closed