go http服务中panic直接返回500是因为http.servehttp默认不recover,panic仅输出到stderr且连接断开;必须用中间件在defer中显式recover并手动写入响应,且recover仅对同goroutine生效。

Go HTTP 服务里 panic 为什么会直接 500?
Go 的 http.ServeHTTP 默认不 recover panic,一旦 handler 内部出错(比如空指针解引用、json.Marshal 遇到不可序列化类型),连接会直接断开,客户端收到空响应或 500,日志里只有 goroutine stack trace,没法定制错误格式或做统一埋点。
用 recover 包一层 handler 就够了吗?
不够。单纯在 handler 函数开头 defer recover() 只能捕获当前 goroutine 的 panic,但 Go HTTP server 启动后,每个请求由独立 goroutine 处理,且 recover 必须和 panic 在同一个 goroutine 才生效。正确做法是包装整个 handler:
- 定义一个中间件函数,接收
http.Handler,返回新的http.Handler - 在
ServeHTTP方法里 defer recover(),并手动写入响应(注意:必须在 panic 前未写 header,否则http.Error会 panic) - 推荐把错误结构体、状态码、日志记录逻辑抽离成可配置函数,而不是硬编码
示例片段:
func RecoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Panic recovered: %v, path: %s", err, r.URL.Path)
}
}()
next.ServeHTTP(w, r)
})
}
如何让自定义错误响应带 JSON 格式和业务码?
默认 http.Error 输出纯文本,不符合 API 规范。你需要手动设置 header 并写入 JSON body,同时注意:一旦调用 w.WriteHeader() 或写入 body,就不能再修改 status code。
- 检查
w.Header().Get("Content-Type")是否为空,避免重复设置 - 用
json.NewEncoder(w).Encode()替代json.Marshal+w.Write,防止内存拷贝 - 建议定义统一错误结构,例如:
type APIError struct { Code int `json:"code"` Message string `json:"message"` } - panic 值可能是
error、string或其他类型,需做类型断言处理,避免fmt.Sprintf("%v", err)泄露敏感信息
recover 无法捕获哪些错误?
recover 只对 panic 生效,对以下情况无效:
- goroutine 死锁(如 channel send/receive 永久阻塞)
- 运行时 fatal error(如栈溢出、内存耗尽)
- HTTP 超时、连接中断、TLS 握手失败等底层网络错误
- 未被 defer 捕获的 panic(比如发生在 goroutine spawn 之后,但没包在 recover 里)
真正健壮的服务还需要配合 http.Server 的 ReadTimeout、WriteTimeout、IdleTimeout,以及用 context.Context 控制 handler 内部逻辑超时——这些和 recover 是正交问题,别指望一个 recover 解决所有异常流。











