Revel 中 recover() 必须在每个请求 goroutine 内部使用,全局或 filter 函数体中直接调用无效;需在 InterceptFunc 中用 defer 包裹,区分 panic 类型映射 HTTP 状态码;异步 goroutine 需单独加 recover;recover 后应立即返回 Result,不可继续执行或重复渲染。

Revel 框架里 recover() 必须写在每个请求 goroutine 内部
Revel 不会自动捕获 panic,哪怕你只在 main() 或初始化函数里加了 defer recover(),也完全无效。HTTP 请求由 Revel 启动独立 goroutine 处理,每个请求的 panic 只能被它自己的 defer 捕获。
常见错误是把 recover 逻辑塞进全局拦截器(app.Init() 阶段注册的 filter),但没确保它运行在请求 goroutine 上——比如写成普通函数调用,或放在 filter 的非 defer 位置。
- ✅ 正确姿势:在 Revel 的
InterceptFunc中,对每个请求 handler 包一层defer func() - ❌ 错误姿势:
if r := recover(); r != nil { ... }直接写在 filter 函数体里(恒返回nil) - ⚠️ 注意:Revel 的
revel.InterceptFunc本身就在请求 goroutine 中执行,所以 defer 是安全的;但别把它和revel.BEFORE等钩子混淆,后者不保证执行上下文
Revel Recovery 中间件要区分 panic 类型并映射 HTTP 状态码
直接对所有 panic 返回 500 Internal Server Error 会掩盖业务语义。比如 panic("not found") 或 panic(&HTTPError{Code: 404}) 应该转成 404 响应,而不是让前端猜。
Revel 的 Result 接口支持自定义响应,推荐在 recover 分支中做类型断言:
- 用字符串 panic 时,检查是否以
"400:"、"404:"开头,提取状态码和消息 - 定义结构体如
type AppError struct{ Code int; Msg string },panic(&AppError{Code: 401, Msg: "unauthorized"}) - 避免
panic(nil)、panic(42)这类无法断言的值——recover 后r.(error)会 panic
示例片段:
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case *AppError:
c.Response.Status = x.Code
c.RenderJSON(map[string]string{"error": x.Msg})
case string:
if strings.HasPrefix(x, "404:") {
c.Response.Status = 404
c.RenderJSON(map[string]string{"error": x[4:]})
} else {
c.Response.Status = 500
c.RenderJSON(map[string]string{"error": "internal error"})
}
default:
c.Response.Status = 500
c.RenderJSON(map[string]string{"error": fmt.Sprintf("unknown panic: %v", x)})
}
}
}()
Revel 中异步 goroutine 的 panic 完全不受中间件保护
Revel 的 HTTP 中间件只能拦住主请求 goroutine 的 panic。一旦你在 controller 里写了 go func() { ... panic("boom") }(),这个 panic 就彻底逃逸,既不会触发中间件 recover,也不会被日志捕获,只会静默终止 goroutine 并可能泄漏资源。
- 必须在每个
go语句内部手动加defer func() { recover() }() - 尤其注意 Revel 的异步场景:模板渲染时调用
revel.RenderAsync、后台任务、定时清理等 - recover 后记得清理资源:关闭 channel、释放锁、调用
conn.Close(),否则可能引发二次 panic 或 goroutine 泄漏
Revel panic 恢复后不能“继续执行”,状态已不可信
recover 只是中断 panic 传播,不回滚任何副作用。如果 panic 发生在数据库事务中间、或已向 channel 发送数据、或已修改全局变量,recover 后若继续执行后续逻辑,大概率导致数据不一致或崩溃。
- recover 分支里应该立即返回
Result(如c.RenderError()或c.RenderJSON()),不要试图“恢复流程” - 别在 recover 后再调用
c.Render()或c.Redirect()—— Revel 的 context 可能已处于半销毁状态 - 打印堆栈要用
debug.Stack(),不是fmt.Printf("%+v", r),否则丢失行号和调用链
最易忽略的一点:Revel 的模板渲染本身可能 panic(比如 nil 切片 range、未定义变量),这类 panic 必须在 Render 调用前就由 controller 的 defer 拦住,否则 response header 已发送,再写 JSON 就会报 http: multiple response.WriteHeader calls。


















