中间件注册必须用 app.Use 而非 app.Middleware,因前者注册全局前置中间件(请求一进来即执行),后者仅作用于已匹配路由的 handler;鉴权、panic 捕获等逻辑若误写在 app.Middleware 中将无法触发。

中间件注册必须用 app.Use,不是 app.Middleware
Buffalo 的中间件链分两层:app.Use 注册的是全局前置中间件(请求一进来就执行),而 app.Middleware 只作用于已匹配路由的 handler。如果你把鉴权或 panic 捕获逻辑写在 app.Middleware 里,它根本不会触发——比如 /api/xxx 路由没定义时,404 就直接返回了,你的中间件压根没机会跑。
常见错误是复制粘贴别人代码时漏掉 app.Use,直接写 app.Middleware(func(...)),结果调试半天发现日志没输出、panic 没捕获、header 没加进去。
- 正确写法:
app.Use(authMiddleware)或app.Use(func(c buffalo.Context) error {...}) - 错误写法:
app.Middleware(authMiddleware)(只对路由匹配后的 handler 生效) - 注册顺序很重要:想做 API 版本校验?必须放在所有业务中间件之前,否则 auth 或 CORS 中间件先报错,版本检查就失效了
怎么手动拦截并返回 401,且带 WWW-Authenticate 头
Buffalo 不会自动加认证头,c.Error(401, err) 只设状态码和响应体,不设 WWW-Authenticate。浏览器看到纯 401 就卡住,不会弹 Basic Auth 对话框。
必须显式设置 header,再调用 c.Render:
authMiddleware := buffalo.MiddlewareFunc(func(c buffalo.Context) error {
token := c.Request().Header.Get("Authorization")
if token == "" || !isValidToken(token) {
c.Response().Header().Set("WWW-Authenticate", `Bearer realm="api"`)
return c.Render(401, r.JSON(map[string]string{"error": "invalid or missing token"}))
}
return nil
})
- 别用
c.Error(401, ...)替代,它不设 header,也不走 renderer 渲染逻辑 - 如果用的是 Basic Auth,realm 值要和客户端一致,否则浏览器可能忽略该头
- 注意:一旦 return,后续中间件和 action 都不执行,适合“终局拒绝”场景
捕获 panic 时别吞掉 buffalo.Error
recover() 会捕获所有 panic,但 Buffalo 内部用 buffalo.Error 表示合法业务异常(如参数校验失败),这类错误本身不是 panic,不该被 recover 干扰。误吞会导致 400 错误变成 500,掩盖真实问题。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
在 recover 分支中加类型判断:
defer func() {
if r := recover(); r != nil {
if _, ok := r.(buffalo.Error); ok {
panic(r) // 让 buffalo.Error 继续走原流程
}
// 真正的 panic:nil pointer、map 写入未初始化等
c.Log().Errorf("panic recovered: %v", r)
debug.PrintStack()
c.Error(500, fmt.Errorf("server error"))
}
}()
- Buffalo 的
buffalo.Error是控制流的一部分,不是 bug,recover 后直接返回 500 会破坏框架语义 - log 和 stack 必须手动打,c.Log() 默认不记录 panic 堆栈
- 不要在 recover 后继续调用
c.Next()或再次写 response,writer 可能已部分写入,二次写入会 panic
上传大文件前必须用 http.MaxBytesReader 包装 Body
Buffalo 默认不限制 multipart 解析大小,用户传个 2GB 文件,服务内存直接爆掉。不能靠前端限制或 Content-Length 判断——那个头可伪造。
必须在中间件里用 http.MaxBytesReader 包装原始 body:
app.Use(func(next buffalo.Handler) buffalo.Handler {
return func(c buffalo.Context) error {
const maxUpload = 10_485_760 // 10MB
c.Request().Body = http.MaxBytesReader(c.Response(), c.Request().Body, maxUpload)
return next(c)
}
})
- 错误会在真正读 body 时触发(比如
c.Request().MultipartForm或c.File调用时),handler 必须捕获http: request body too large或io.ErrUnexpectedEOF - 赋值
c.Request().Body必须在next(c)前完成,否则无效 - 这个中间件得放最外层,否则其他中间件(如日志)先读 body,就绕过限制了
实际项目里最容易被忽略的,是 panic 恢复后没记录上下文、上传限制没生效却以为前端校验够用了、以及把 buffalo.Error 当成 panic 处理——这三处一出问题,线上排查时间翻倍。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










