recover必须放在defer中且defer必须在next.servehttp()前注册,否则无法捕获panic;recover仅对当前goroutine有效,跨goroutine panic需业务层自行处理。

recover必须放在defer里,且defer必须在next.ServeHTTP()之前注册
Go 的 recover() 不是监听器,它只在 panic 发生后、当前 goroutine 栈尚未完全展开时,于 defer 函数中**直接调用**才有效。写成普通函数调用(比如放在 next.ServeHTTP() 后面或单独定义一个 handlePanic() 函数再调)永远返回 nil。
常见错误是把 recover() 放在中间件主逻辑里,或者 defer 块写在 next.ServeHTTP(w, r) 之后——这时 panic 已经发生并完成栈展开,recover() 拿不到任何东西。
- 正确姿势:
defer func() { if err := recover(); err != nil { /* 处理 */ } }()必须出现在next.ServeHTTP(w, r)调用前 - 不要用变量保存
recover()结果再判断,必须原样调用:recover(),不能err := recover()后再if err != nil - 如果中间件本身 panic(比如日志写入失败),这个
defer也能捕获——但前提是它已注册
recover后必须显式写响应,否则客户端会卡住等待超时
标准库的 http.ServeHTTP 不会自动处理 panic;一旦 handler panic,连接可能被静默关闭,客户端收不到任何响应,最终触发超时。所以 recover() 后不能只打日志就完事,必须手动控制响应流。
使用 4 个 agent 部署一套备份与灾难恢复系统。在以下情况下使用该技能: 1. 用户希望搭建自动化的备份基础设施; 2. 用户正在配置……
- 优先用
http.Error(w, "Internal Server Error", http.StatusInternalServerError),它会自动设置 header 并写 body - 如果下游 handler 已经写过 header(比如状态码 200),再调
http.Error()会 panic 报http: multiple response.WriteHeader calls - 更安全的做法是封装一个
writeError(w, err)工具函数,内部先检查w.Header().Get("Content-Type") == ""再决定是否调w.WriteHeader() - 别忘了记录 panic 堆栈:
log.Printf("PANIC in %s %s: %+v", r.Method, r.URL.Path, err)+debug.PrintStack()(仅开发环境)
中间件没真正 wrap 到最终 handler 就等于没加
很多 panic 看似“没被捕获”,其实是中间件根本没执行到。常见失效场景包括:
- 用了
http.HandleFunc("/", handler)直接注册,绕过了你写的中间件链,导致Recovery完全不生效 - 第三方框架(如 Gin、Echo)自带 recovery 中间件,你的自定义
Recovery可能被跳过,或执行顺序不对(比如你放在了 auth 之后,而 auth 里 panic 了却没被你捕获) - handler 是自定义 struct 实现的
http.Handler,但中间件签名写成了func(http.HandlerFunc) http.HandlerFunc,类型不匹配导致编译通过但运行时 panic - 中间件返回值类型错误:必须是
func(http.Handler) http.Handler,不能漏掉return,也不能返回裸函数而不转成http.HandlerFunc
recover只对当前 goroutine 有效,跨 goroutine panic 捕不到
如果你的 handler 里启了新 goroutine(比如异步发消息、定时清理),那个 goroutine 里 panic,外层中间件的 defer+recover 是完全无感的。这类 panic 会直接终止该 goroutine,还可能泄露资源或留下脏数据。
- 不要指望中间件兜底所有 panic——它只管请求生命周期内的主 goroutine
- 业务代码里涉及 goroutine 的地方,自己加
defer+recover,或改用errgroup等可取消、可等待的机制 - 第三方库如果内部开 goroutine 并 panic,得看它是否暴露错误回调;否则只能靠进程级监控(如 systemd 自动重启)兜底
- 最常漏掉的是:recover 后没检查响应是否已写,就直接调
http.Error(),结果 panic 报http: wrote more than the declared Content-Length或 header 冲突
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










