go http handler 中 panic 默认导致500且不记录堆栈,需手动 recover 并调用 debug.printstack;error 返回不等于http状态码,须显式设置;错误响应应统一结构并过滤敏感信息。

Go HTTP handler 里 panic 会直接 500,但默认不记录堆栈
Go 的 http.ServeHTTP 不会自动 recover panic,一旦 handler 内部 panic,连接就直接断开,客户端只看到空响应或 500,日志里也没线索。这不是“没报错”,是错被吞了。
- 必须手动在中间件或顶层 handler 包一层
recover(),否则所有 panic 都静默失败 -
recover()只在 defer 中有效,且仅对当前 goroutine 生效 —— 别指望它能抓到子 goroutine 里的 panic - 别只打印
err,要调用debug.PrintStack()或用runtime/debug.Stack()拿完整堆栈,否则定位不到哪行出的错 - 示例:在自定义中间件中写
func Recovery(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Printf("PANIC in %s %s: %+v", r.Method, r.URL.Path, err) debug.PrintStack() http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }
error 返回 ≠ HTTP 错误状态码,别混淆语义
Go 函数返回 error 是控制流的一部分,和 HTTP 状态码无关。http.Error() 或 w.WriteHeader() 才真正决定响应头里的 Status。常见错误是:函数返回了 fmt.Errorf("not found"),但 handler 里没调用 w.WriteHeader(http.StatusNotFound),结果还是 200 + 错误文本。
- 显式错误类型比字符串判断更可靠:定义
type AppError struct { Code int; Err error },在中间件统一检查并设置状态码 - 避免在业务逻辑里直接调用
http.Error()—— 这会让 handler 失去控制权,也难测试;应让 handler 自己决定如何渲染错误 - 如果用了 Gin/Echo 等框架,它们的
c.AbortWithStatusJSON()或c.Error()是封装好的,但底层仍依赖你正确传递状态码,不是靠 error 值自动推导
全局错误响应格式不统一,前端解析容易崩
后端返回 {"error": "xxx"}、{"message": "xxx", "code": 1001}、甚至纯文本,前端得写一堆条件分支来处理,稍有遗漏就 crash 或静默失败。
- 强制所有错误响应走同一结构体,比如:
type ErrorResponse struct { Code int `json:"code"` Message string `json:"message"` TraceID string `json:"trace_id,omitempty"` } - 在中间件统一序列化错误,不要每个 handler 都
json.Marshal()一遍;尤其注意 content-type 必须设为application/json - 400/401/403/404/500 这些状态码对应不同语义,别全用 500 —— 例如参数校验失败该用 400,未登录该用 401,权限不足该用 403
- 生产环境别暴露
runtime.Caller或原始 error message 给前端,敏感信息(如数据库表名、路径)必须过滤
panic 恢复后继续写响应可能 panic,w.Header().Set() 会报错
recover 之后,http.ResponseWriter 的状态可能已部分写入(比如 header 已发),此时再调用 w.WriteHeader() 或 w.Write() 会 panic 报 http: multiple response.WriteHeader calls 或 http: superfluous response.WriteHeader call。
立即学习“go语言免费学习笔记(深入)”;
- 恢复 panic 后第一件事是检查
w.Header().Get("Content-Type")是否为空,或者用struct{ http.ResponseWriter }包装器提前拦截写操作(推荐 go-chi/render 等成熟方案) - 更稳妥的做法:recover 后立即构造新响应,用
io.Discard替换原ResponseWriter,确保不会二次写入 - 别在 defer 里做耗时操作(如写日志到网络服务),panic 恢复路径本就不可靠,再加 IO 失败会导致错误进一步掩盖










