HTTP handler 中 panic 会导致客户端断连,因 panic 发生在 handler goroutine 内,server 主循环不捕获,响应未写出即连接重置;必须每个 handler 内单独 defer recover 并立即返回错误响应。

Go 的 recover 无法跨 goroutine 生效,每个 HTTP handler 或 worker 必须自己加 defer + recover,否则一个请求 panic 会静默退出、丢任务、漏日志,但不会崩进程。
为什么 HTTP handler 里 panic 会导致客户端断连
默认的 Go http.ServeMux 和多数框架(如 Gin、Echo)在 handler panic 后不写响应头或状态码,TCP 连接直接被关闭。客户端收不到 500,只看到 EOF 或超时。
- 现象:日志没报错,但前端反复 499 / 502,
curl -v显示 connection reset - 根本原因:panic 发生在 handler goroutine 内,而主 server 循环只负责 accept 和 dispatch,不拦截子 goroutine 的 panic
- 别指望中间件“统一包一层”——中间件函数本身在 handler goroutine 里执行,
recover必须写在 handler 函数内部或其调用链的 defer 中
每个 handler 函数必须独立 defer recover
不能只在 main 或 router 初始化时写一次 defer func() { recover() }(),那对任何 handler 都无效。
- 正确写法是每个 handler 函数开头就注册:
defer func() { if r := recover(); r != nil { log.Printf("handler panic: %v", r) } }() - 如果 handler 是闭包或方法,确保
defer在函数体最顶部,且在可能 panic 的逻辑(如 JSON 解析、DB 查询)之前 - 使用命名返回值可透出错误:
func myHandler(w http.ResponseWriter, r *http.Request) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }()
recover 后别继续执行业务逻辑
recover 只是让 goroutine “软着陆”,不是回滚或重试。数组越界后 slice 可能已损坏,map 并发写入后内部哈希表可能处于不一致状态。
立即学习“go语言免费学习笔记(深入)”;
- 常见错误:recover 后还调用
json.NewEncoder(w).Encode(data),但 data 里字段刚被空指针解引用破坏过 - 安全做法:recover 后立即
return,或写个固定错误响应:http.Error(w, "Internal Server Error", http.StatusInternalServerError) - 切勿在 recover 块里 close 已 panic 的 channel、写入已失效的文件句柄、或释放未 acquire 的锁
高并发下 panic 日志要防阻塞
成百上千个 goroutine 同时 panic,如果每个都直接 log.Printf,I/O 竞争会让服务卡住甚至雪崩。
- 推荐用带缓冲 channel 统一收集:
panicCh = make(chan error, 1000) - handler 里 recover 后只发消息:
panicCh - 单独起一个 goroutine 消费:
go func() { for err := range panicCh { log.Printf("PANIC: %v", err) } }() - 注意 shutdown 时关闭 channel:
close(panicCh),避免消费者死锁
真正难的不是写对 recover,而是判断哪些 panic 值得 recover——比如 nil pointer dereference 或 index out of range 说明代码有 bug,该修;而 context canceled 不该 panic,更不该 recover。


















