go 中“错误恢复中间件”本质是用 recover() 拦截 handler 中的 panic 并返回统一响应,因 http.servehttp 不自动 recover,需手动在 handler 内 defer 调用 recover;它仅捕获同 goroutine panic,不可跨协程;恢复后应记录堆栈、按 panic 类型设状态码(如 validationerror 取 code),并以 json 返回错误,避免二次 panic。

Go 没有传统意义上的“中间件”概念,也没有 try/catch,所以所谓“错误恢复中间件”,本质是利用 recover() 在 HTTP handler 调用链中拦截 panic,并统一返回错误响应。它不能捕获普通 error,只对 panic 有效。
为什么 http.HandlerFunc 需要手动包装才能 recover
HTTP server 启动后,http.ServeHTTP 内部调用 handler 函数,但不会包裹 defer/recover。一旦 handler panic,goroutine 崩溃,连接被关闭,服务端日志只显示 “panic: …” 而无响应,客户端拿到的是 EOF 或 500 空响应。
- 必须在 handler 执行前主动加
defer func() { if r := recover(); r != nil { … } }() - 不能直接给
http.HandleFunc传原始函数,得包装成带 recover 的新 handler - 恢复后应记录 panic 堆栈(用
debug.PrintStack()或runtime/debug.Stack()),否则问题难以定位
recover() 必须在 defer 中且与 panic 同 goroutine
这是最常踩的坑:把 recover() 放在独立函数里、或在 goroutine 中调用 handler,都会导致 recover 失效。
- ❌ 错误写法:
go func() { defer recover() }()—— 新 goroutine 无法 recover 原 goroutine 的 panic - ❌ 错误写法:
func doRecover() { defer recover() }—— recover 不在 panic 所在栈帧的 defer 中 - ✅ 正确位置:handler 函数体内第一行写
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - 注意:recover 只能捕获当前 goroutine 的 panic,跨 goroutine panic 需用 channel + select 或第三方库(如 errgroup)协调
如何返回结构化错误响应并保持状态码合理
recover 后不能继续执行原 handler 逻辑,只能终止流程、写响应。常见错误是统一返回 500,但有些 panic 实际对应客户端错误(如 JSON 解析失败引发的 panic,其实是 400)。
立即学习“go语言免费学习笔记(深入)”;
- 默认情况一律设
w.WriteHeader(http.StatusInternalServerError) - 若 panic 值是自定义错误类型(如
type ValidationError struct{ Code int }),可类型断言后取状态码:if ve, ok := r.(ValidationError); ok { w.WriteHeader(ve.Code) } - 响应体建议用 JSON:
json.NewEncoder(w).Encode(map[string]string{"error": fmt.Sprintf("%v", r)}) - 避免在 recover 里再 panic(比如 JSON 编码失败),否则会彻底崩溃;可先序列化为字符串再写入
w.Write()
真正难的不是加 recover,而是区分哪些 panic 是程序 bug(该修复)、哪些是预期外输入触发(该转为 4xx 响应)。recover 只是兜底,不能替代参数校验和 error 处理。










