recover不生效主因有三:defer未在panic前注册、recover不在defer内、跨goroutine调用;http中需在handler入口立即defer并内嵌recover,且须手动写500响应头与脱敏响应体。

Go HTTP 中间件里 recover 为什么总不生效
recover 失效基本就三类原因:defer 没在 panic 前注册、recover 不在 defer 函数体内、跨 goroutine 调用。HTTP handler 每个请求跑在独立 goroutine,所以 recover() 必须放在该 goroutine 的 defer 里,且 defer 要在任何可能 panic 的代码之前执行。
常见错误写法:func Recovery(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { /* 先调 next.ServeHTTP,再 defer */ }) }——panic 发生时 defer 还没注册,直接崩。
正确姿势是:
- defer 必须紧贴函数入口,写在
next.ServeHTTP(w, r)之前 - recover() 必须写在 defer 的匿名函数内部,不能拆出去
- 别依赖第三方 router 的默认 Recovery(比如
gin.Recovery()),它只打日志、不写响应体,客户端会卡在 pending 状态
自定义 Recovery 中间件必须手动写响应头和响应体
recover 后不调 w.WriteHeader(500),响应状态码仍是默认的 200;不写响应体,客户端收不到数据,前端 fetch 会超时失败。这不是“处理完了”,而是“假装处理了”。
生产环境还必须脱敏:别直接 fmt.Fprint(w, err),stack trace 里的路径、行号、变量值都得过滤。可用 debug.Stack() 获取原始堆栈,再正则替换掉敏感字段。
示例关键逻辑:
defer func() {
if r := recover(); r != nil {
w.WriteHeader(500)
// 脱敏后写 JSON
json.NewEncoder(w).Encode(map[string]string{
"error": "Internal Server Error",
"trace_id": getTraceID(r),
})
log.Printf("panic recovered: %v, stack: %s", r, sanitizeStack(debug.Stack()))
}
}()
Gin 框架下替换默认 Recovery 的实际做法
gin.Recovery() 是个摆设中间件:它 recover 了 panic,但只调 log.Printf,然后啥也不干。你看到的 {"error": "Internal Server Error"} 是 Gin 内部兜底写的,不是你控制的。
要真正接管,得自己实现一个中间件,且注意两点:
- 必须在
gin.Default()之后立即用r.Use(YourRecovery())替换掉默认的gin.Recovery() - 你的中间件里别再调
c.Abort()或c.Next()——那是 Gin 的上下文控制,recover()后原 handler 已中断,继续c.Next()会重复执行或 panic
更稳妥的方式是直接包装 http.Handler,绕过 Gin 的中间件链,避免被其他中间件干扰。
非 HTTP 场景(如 TCP 服务)的 panic 兜底位置在哪
TCP 长连接场景下,每个 client 对应至少一个 goroutine(读/写各一)。panic 若发生在这些 goroutine 里,不加 recover 就整进程退出——所有用户瞬间掉线。
兜底点非常明确:每个长寿命 goroutine 的最顶层函数开头加 defer recover。
- 比如
func (s *Server) handleConn(conn net.Conn),第一行就是 defer - 广播 goroutine、心跳协程、定时清理协程,全都要单独加
- recover 后别只 log,得做资源清理:关 conn、删 map 中的 user、关闭对应 channel
注意:recover 不会回滚已发生的副作用。DB 已提交的事务、MQ 已发的消息、缓存已删的 key,都不会自动撤销——兜底只是保命,不是事务补偿。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











