因为statuscode()是响应写入操作,而中间件panic发生在http处理链中断时,上下文已失效,无法写入;必须用userouter注册兜底recover中间件拦截路由匹配后的panic。

为什么 iris.Context.StatusCode() 不能捕获中间件抛出的 panic
因为 Iris 的 MVC 控制器方法在执行时被框架自动包裹在 recover() 中,但中间件(尤其是自定义中间件)若在控制器调用前 panic,会直接跳出 HTTP 处理链,StatusCode() 或 WriteStatus() 已无上下文可写。常见现象是日志里出现 http: panic serving,但客户端收到 500 且无自定义错误体。
正确做法是统一用 app.UseRouter() 注册兜底 recover 中间件,并确保它位于所有其他中间件之后:
app.UseRouter(func(ctx iris.Context) {
defer func() {
if r := recover(); r != nil {
ctx.StatusCode(500)
ctx.JSON(iris.Map{"error": "internal server error"})
}
}()
ctx.Next()
})
- 必须用
UseRouter(不是Use),否则无法拦截路由匹配后的 panic - 这个中间件要放在
app.RegisterView()、app.ConfigureContainer()等初始化之后,且在app.Handle()或app.Controller()之前注册 - 不要在控制器里手动
panic()—— 改用ctx.StopWithStatus(400, "bad request")
控制器方法里怎么返回 400/404 而不中断整个请求流
Iris MVC 的控制器方法默认返回值会被自动序列化,但错误处理需要主动终止当前流程并设状态码。直接 return 不够,必须用 ctx.StopExecution() 阻止后续逻辑,再配 ctx.StatusCode() 和响应体:
func (c *UserController) GetBy(id string) interface{} {
uid, err := strconv.Atoi(id)
if err != nil {
c.Ctx.StatusCode(400)
c.Ctx.WriteString("invalid user id")
c.Ctx.StopExecution()
return nil // 必须显式 return,否则 Iris 仍会尝试序列化返回值
}
user, found := findUser(uid)
if !found {
c.Ctx.StatusCode(404)
c.Ctx.JSON(iris.Map{"error": "user not found"})
c.Ctx.StopExecution()
return nil
}
return user
}
-
StopExecution()是关键,漏掉会导致状态码和响应体被覆盖(例如框架后续写入 200 + JSON) - 不要用
panic("not found")模拟 404 —— 这会触发全局 recover,丢失语义且难定位 - 如果控制器方法签名是
func() error,可配合app.ConfigureContainer()注入错误处理器,但不如显式控制流直观
如何让验证失败自动转成 400 并返回字段错误
Iris 自带的 context.ReadJSON() 不校验结构体 tag,需手动集成 validator(如 go-playground/validator)。重点在于:验证错误必须在绑定后立即检查,且错误格式要对齐前端预期:
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2"`
Email string `json:"email" validate:"required,email"`
}
func (c *UserController) Post() interface{} {
var req CreateUserRequest
if err := c.Ctx.ReadJSON(&req); err != nil {
c.Ctx.StatusCode(400)
c.Ctx.JSON(iris.Map{"error": "invalid JSON"})
c.Ctx.StopExecution()
return nil
}
if err := validator.New().Struct(req); err != nil {
var fields []string
for _, e := range err.(validator.ValidationErrors) {
fields = append(fields, e.Field()+" is "+e.Tag())
}
c.Ctx.StatusCode(400)
c.Ctx.JSON(iris.Map{"errors": fields})
c.Ctx.StopExecution()
return nil
}
return createUserService(req)
}
- 别依赖
ReadJSON自动校验 —— 它只负责反序列化,不读取validatetag - 错误字段名用
e.Field()而非e.StructNamespace(),避免返回嵌套路径如User.Name - validator 实例建议复用(如全局变量或依赖注入),避免每次新建开销
为什么 ctx.View() 错误不会触发 status 404 而是 500
当模板文件不存在时,ctx.View("missing.html") 默认 panic,Iris 将其转为 500。这不是 bug,而是设计:视图渲染属于“服务端内部错误”,而非客户端请求错误。若想对缺失模板返回 404,必须手动检查:
func (c *HomeController) Get() interface{} {
tmplPath := "home.html"
if !c.App().View.IsExists(tmplPath) {
c.Ctx.StatusCode(404)
c.Ctx.WriteString("template not found")
c.Ctx.StopExecution()
return nil
}
return iris.Map{"title": "Home"}
}
-
App().View.IsExists()是唯一可靠判断方式,不要用os.Stat()—— Iris 支持嵌入文件、压缩包等多后端 - 静态资源(如 CSS/JS)404 应由
app.StaticWeb()自动处理,无需手写逻辑 - 模板语法错误(如
{{.Foo.Bar}}中 Bar 为空)仍会 panic 成 500,这类属于开发期问题,上线前应充分测试
Use() 里,结果路由未匹配就 panic 了,根本进不到控制器;还有人以为 StopExecution() 可以省略,结果状态码和响应体被框架默认行为覆盖。这些点不踩一遍很难记住。











