recover必须在defer函数中调用,因为仅在defer执行期间有效且只能捕获当前goroutine的panic;普通调用或非defer中调用始终返回nil。

为什么 recover 必须在 defer 函数里调用
因为 recover 只有在 defer 执行期间才有效,且仅能捕获当前 goroutine 的 panic。如果写成普通函数调用,或放在非 defer 语句中,recover 永远返回 nil,起不到恢复作用。
常见错误写法:
func badMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ❌ 错误:recover 不在 defer 中,永远无效
if err := recover(); err != nil {
log.Printf("panic recovered: %v", err)
}
next.ServeHTTP(w, r)
})
}
正确结构必须是:
-
defer包裹一个匿名函数 - 该匿名函数内第一行就调用
recover() - 不能跨 goroutine(比如在
go func() { ... }()里 recover)
HTTP 中间件中安全封装 recover 的典型模式
核心是把 panic 捕获、日志记录、错误响应三件事串起来,同时避免影响原有 response 流程(比如 header 已写入时不能再写 body)。
立即学习“go语言免费学习笔记(深入)”;
实操要点:
- 用
http.NewResponseWriter或自定义responseWriter拦截写入,判断Header().Get("Content-Type")是否已设置,防止http.Error报http: multiple response.WriteHeader calls - panic 值可能是
string、error、nil,建议统一转为fmt.Sprintf("%v", r)输出,避免类型断言失败 - 记录 panic 时带上堆栈:
debug.PrintStack()或runtime/debug.Stack(),但注意后者返回[]byte,需转string
简短示例:
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in %s %s: %v", r.Method, r.URL.Path, r)
log.Printf("Stack trace:\n%s", debug.Stack())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
如何避免中间件里 panic 恢复后继续执行后续逻辑
recover 只是“停止 panic 传播”,并不会自动跳过 next.ServeHTTP 后的代码。如果没控制好流程,可能造成重复写 response 或状态不一致。
关键点:
-
recover()后必须显式 return,否则会继续往下走 - 不要在 defer 函数里做业务重试或 fallback 处理——这不是 recover 的职责
- 若需定制错误页,用模板渲染代替
http.Error,并确保只写一次w.WriteHeader
容易被忽略的坑:
比如下面这段代码会在 panic 后仍执行 log.Println("after serve"):
defer func() {
if r := recover(); r != nil {
http.Error(w, "Oops", 500)
// ❌ 缺少 return,后续代码照常执行
}
}()
next.ServeHTTP(w, r)
log.Println("after serve") // 这行仍会执行
生产环境要不要对所有 panic 都 recover
不是。recover 是兜底手段,不该替代正常错误处理。比如数据库连接失败、JSON 解析错误这些明确可预判的场景,应该用 if err != nil 显式处理并返回对应 HTTP 状态码。
真正需要 recover 的,只有那些无法静态检查、运行时突变导致的崩溃,例如:
- 空指针解引用(
nil pointer dereference) - 切片越界(
index out of range) - 向已关闭 channel 发送数据
- 递归过深导致栈溢出(极少见,但 Go 1.22+ 有改进)
过度使用 recover 会让问题更难定位——它掩盖了本该修复的 bug,还可能让程序处于不可预期状态。上线前应配合 go vet 和单元测试尽量减少 panic 触发面。










