Gin无法用recover捕获连接中断错误,因其属于I/O error而非panic;需通过自定义ResponseWriter包装器在c.Next()后检查write error并记录日志。

Gin 本身不会主动“断开连接”并抛出可捕获的异常——你看到的连接中断(如 broken pipe、connection reset by peer)是底层 net/http 在写响应时发生的 I/O 错误,它发生在 handler 返回之后,**无法用 defer+recover 捕获**。
为什么 recover() 抓不到 “连接已断开” 错误
这类错误不是 panic,而是 http.ResponseWriter.Write() 或 c.JSON() 等调用返回的 error。Gin 默认忽略这些 error,也不暴露给业务代码。你写的 handler 已执行完毕,recover() 早已退出,此时再发生 write 失败,goroutine 已结束,无处拦截。
-
panic是运行时崩溃,recover()只能捕获它 - 连接断开是 syscall 层的
EPIPE或ECONNRESET,属于error值,不是 panic - Gin 的
c.JSON()/c.String()内部调用WriteHeader+Write,失败时只 log 一句(默认关闭),不 panic,也不返回 error 给你
如何检测并记录客户端提前断连
唯一可行方式:在中间件或 handler 结束后,检查 c.Writer.Written() 和底层 ResponseWriter 是否报错——但 Gin 不直接暴露 write error,需借助 gin.ResponseWriter 的包装特性。
推荐做法:用自定义 ResponseWriter 包装器,在 Write() 和 WriteHeader() 后检查 error:
type loggingResponseWriter struct {
gin.ResponseWriter
wroteHeader bool
err error
}
func (w *loggingResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
w.wroteHeader = true
}
func (w *loggingResponseWriter) Write(b []byte) (int, error) {
n, err := w.ResponseWriter.Write(b)
if err != nil && w.err == nil {
w.err = err
}
return n, err
}
func LogDisconnect() gin.HandlerFunc {
return func(c *gin.Context) {
w := &loggingResponseWriter{
ResponseWriter: c.Writer,
wroteHeader: false,
err: nil,
}
c.Writer = w
c.Next()
if w.err != nil {
if strings.Contains(w.err.Error(), "broken pipe") ||
strings.Contains(w.err.Error(), "connection reset by peer") {
log.Printf("[DISCONNECT] client %s closed early: %v", c.ClientIP(), w.err)
}
}
}
}
- 必须在
c.Next()之后读w.err,不能在 handler 内判断 - 不要依赖
c.Writer.Status()—— 它可能为 0 即使 header 已发,因为 status 是缓存值 - 该 error 仅用于日志,**不能用来 abort 或重写响应**:响应已发出(或尝试发出),HTTP 状态码和 body 都不可逆
常见误判场景与真实错误信号
别把以下现象当成可处理的“异常”去 recover 或拦截:
-
read: connection timed out:发生在c.BindJSON()等读请求体时,会直接返回 400,不是 panic,也无需 recover -
http: response.WriteHeader on hijacked connection:说明用了 WebSocket 或长连接升级,此时不能再调用c.JSON() -
context canceled:来自c.Request.Context().Done(),应在 handler 中主动监听 ctx,而非等 write 失败 - 前端 F5 刷新、关 tab、关浏览器 —— 这些触发的断连,绝大多数情况下无需业务响应,只需安静记日志
真正需要干预的,是那些影响服务稳定性的重复断连(比如某类设备频繁重连)、或伴随大量 broken pipe 的上游 LB 配置问题。单纯捕获单次断连并试图“友好提示”,既做不到,也没意义——响应都发不出去,还怎么提示?


















