
本文详解如何在 martini 框架中正确注册 `recoverwrap` 中间件,实现对所有路由处理器 panic 的统一捕获与响应,避免服务崩溃,并指出使用 `martini.context.next()` 的关键原理及注意事项。
Martini 是一个轻量级 Go Web 框架,其中间件机制基于依赖注入与执行链(Context.Next()),并非标准 http.Handler 装饰器模式。因此,直接将 func(http.Handler) http.Handler 类型的函数传给 m.Use() 会导致编译失败——因为 Martini 的 injector 无法解析 http.Handler 作为参数注入。
✅ 正确做法是:编写符合 Martini 中间件签名的函数,接收 martini.Context 和 http.ResponseWriter(或其他可注入类型),并在 defer 中调用 recover(),最后显式调用 c.Next() 触发后续处理器执行:
func RecoverWrap(c martini.Context, w http.ResponseWriter) {
defer func() {
if r := recover(); r != nil {
var err error
switch v := r.(type) {
case string:
err = errors.New(v)
case error:
err = v
default:
err = errors.New("unknown panic value")
}
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
// 注意:此处不返回,确保响应已写出且链终止
}
}()
c.Next() // 关键:继续执行后续 handler(如路由函数)
}? 重要注意事项:
- 注册顺序至关重要:m.Use(RecoverWrap) 必须在所有其他中间件和路由注册之前调用(推荐紧接 martini.Classic() 后),否则在其之前注册的中间件中发生的 panic 将无法被捕获;
- 无需手动传入 http.Request:Martini 会自动注入 *http.Request、http.ResponseWriter、martini.Context 等常用对象,按需声明即可;
-
生产环境建议直接使用内置 martini.Recovery:Martini 已提供成熟、带日志输出的恢复中间件:
m.Use(martini.Recovery()) // 默认记录 panic 到 log,返回 500
其源码逻辑与上述自定义实现一致,且支持自定义日志器(如 martini.RecoveryWithWriter(io.Writer))。
? 总结:Martini 的中间件本质是“执行链钩子”,依赖 Context.Next() 控制流程,而非 HTTP Handler 套娃。掌握这一模型,才能正确实现日志、鉴权、恢复等通用能力。切勿尝试适配 http.Handler 签名——那是 net/http 或 Gin/Chi 等框架的范式。










