HTTP handler 中必须每个都加 defer+recover,因 recover 只对当前 goroutine 生效;panic 后需显式清理资源,且 recover 返回值为 interface{},应使用 fmt.Sprintf("%v", r) 安全记录。

recover 不能直接用于 HTTP 连接容错,它只对当前 goroutine 生效;HTTP handler 中的 panic 必须在该 handler 内部用 defer+recover 捕获,否则连接会静默断开、资源不释放。
HTTP handler 中必须每个都加 defer+recover
Go 的 http.ServeMux 或框架(如 Gin、Echo)为每个请求启动独立 goroutine。一旦 handler 内 panic,若没在该 goroutine 里调用 recover(),整个连接就直接关闭,客户端收不到响应,服务端也无日志——这不是“连接容错”,是静默失败。
- 错误写法:
main()里写一个全局defer recover()—— 它捕获不到任何 handler 的 panic - 正确做法:每个 handler 函数开头就注册
defer func() { if r := recover(); r != nil { /* 记录 + 返回 500 */ } }() - 框架用户注意:Gin 默认启用
recovery()中间件,但 Echo 需手动注册middleware.Recover(),自定义 mux 则完全靠自己补
recover 后必须显式清理资源,否则连接卡死
panic 发生时,defer 链仍执行,但仅限当前 goroutine 已注册的 defer。如果 handler 里打开了数据库连接、文件句柄或调用了 http.Client.Do(),这些资源不会自动释放。
- 常见泄漏点:
*sql.Conn未Close()、os.File未Close()、net.Conn未Close() - 必须在
recover分支里做清理:if r != nil { dbConn.Close(); file.Close(); w.WriteHeader(http.StatusInternalServerError) } - 不要在
recover后继续使用已 panic 的对象(比如 panic 是因user.Name为空指针,recover 后再访问仍会 crash)
recover 返回值类型不确定,不能直接断言 error
recover() 返回 interface{},实际类型取决于 panic() 传入的参数。可能是 string、*runtime.Error、自定义 struct,甚至 int。用 r.(error) 强制断言会触发二次 panic。
立即学习“go语言免费学习笔记(深入)”;
- 安全日志方式:
fmt.Sprintf("%v", r)—— 总能转成可读字符串 - 需要区分错误类型时,先用
switch v := r.(type)分支处理,例如:case string: log.Printf("panic string: %s", v)、case error: log.Printf("panic error: %v", v) - 避免依赖 panic 值做业务逻辑分支,它只是崩溃现场快照,不是设计好的错误信号
goroutine 异步调用必须单独包一层 safeGo
handler 中起 goroutine 做异步操作(如发消息、写日志、调第三方 API)时,那个 goroutine 的 panic 完全独立于 handler,主 goroutine 的 recover 对它无效。
- 必须封装:
safeGo(func() { defer func() { if r := recover(); r != nil { log.Printf("async panic: %v", r) } }(); doAsyncWork() }) - 不能只靠 handler 的 recover 覆盖所有路径;HTTP 连接已返回,异步 panic 只影响后台任务,但资源(如
http.Client连接)可能堆积 - 异步任务中打开的资源(如临时文件、锁)同样要在自己的
recover分支里释放
真正能守住连接的不是 recover 本身,而是它配合的时机、作用域和清理动作。漏掉任意一环,都会让“容错”变成“掩盖泄漏”。


















