超时中间件必须置于路由匹配后、handler执行前,通过ctx.timeout包裹ctx.next()实现;不可在goroutine中调用ctx.next(),须将timeoutctx透传至db查询、http调用等下游操作以确保安全终止。

超时中间件必须放在路由匹配之后、handler执行之前
Iris 的中间件执行顺序是「注册顺序」而非「声明顺序」,app.Use() 全局注册的中间件会作用于所有路由,但若你用 app.Get("/api/foo", timeoutMW, handler) 这种链式写法,则 timeoutMW 一定在 handler 前执行——这是超时控制生效的前提。常见错误是把超时逻辑写进 handler 内部(比如用 time.AfterFunc),结果 panic 发生时已进入业务层,无法统一中断。
关键约束:ctx.Next() 后续的 handler 执行仍在同一个 goroutine,所以能用 context.WithTimeout 包裹;但绝不能在 go func() { ctx.Next() }() 里调用,否则超时 cancel 无法影响到新 goroutine 中的 handler。
用 context.WithTimeout 包裹 handler 执行,不是包装 HTTP server
Iris 本身不提供类似 http.Server.ReadTimeout 那样的连接级超时,接口级超时必须由中间件在请求生命周期内主动控制。正确做法是在中间件中创建带超时的子 context,并通过 ctx.SetUserValue("timeoutCtx", timeoutCtx) 透传下去(如果后续 handler 需要主动检查),但更常用的是直接包裹 ctx.Next():
func timeoutMiddleware(timeout time.Duration) iris.Handler {
return func(ctx iris.Context) {
ctx.Timeout(timeout, func() {
// 超时后执行:清理资源、记录日志、返回 504
ctx.StatusCode(504)
ctx.WriteString("Request timeout")
}, func() {
// 正常完成时执行(可选)
ctx.Next()
})
}
}
注意:ctx.Timeout 是 Iris 封装好的方法,底层就是 context.WithTimeout + select 监听,不要自己重复造轮子。
- 别用
http.Server.WriteTimeout替代——它控制的是响应写出阶段,对 handler 执行耗时无约束 - 超时时间建议按接口分级设置,比如查询类 5s,导出类 60s,避免一刀切
- 超时触发时,Iris 不会自动关闭 underlying connection,但 client 通常已断开,无需额外处理
超时后如何安全终止 handler 中的阻塞操作
单纯返回 504 并不等于停止 handler 内部正在运行的逻辑。如果 handler 里有数据库查询、HTTP 调用或循环计算,它们仍会继续执行,浪费资源甚至写入脏数据。
必须让下游操作感知超时 context:
- DB 查询:传入
ctx.Request().Context()(注意不是iris.Context)给db.QueryContext() - HTTP 调用:用自定义
http.Client,其Timeout或Do(req.WithContext(timeoutCtx)) - 手动循环:在循环体内定期检查
select { case
误区:ctx.Cancel() 不存在——Iris 的 Context 不是 Go 原生 context.Context,不能直接 cancel。必须用 ctx.Request().Context() 获取可取消的 context 实例。
别忽略并发场景下的 panic 捕获和日志标记
超时中间件内部的 ctx.Timeout 在触发回调时,如果 handler 已 panic,Iris 默认 recovery 中间件可能来不及捕获。此时超时回调里的 ctx.WriteString 会 panic,整个请求流程崩溃。
实操建议:
- 确保
iris.Recover在超时中间件之前注册(app.Use(iris.Recover, timeoutMW)) - 在超时回调函数里加
defer func() { _ = recover() }()防止二次 panic - 日志中显式标记超时请求:
log.Printf("TIMEOUT: %s %s, took %v", ctx.Method(), ctx.Path(), time.Since(start))
最易被忽略的一点:超时中间件对 WebSocket 升级请求无效,因为 ctx.Next() 不会进入常规 handler 流程,这类长连接需单独用 conn.SetReadDeadline 控制。











