http层超时无法控制handler内grpc/db等调用,必须用context.withtimeout包裹所有i/o操作并显式传入;中间件需select监听ctx.done()后立即statuscode(408)+stopexecution();body读取前须检查ctx.err()并重置。

为什么不能只靠 HTTP 层超时
Iris 的 ctx.SetReadTimeout() 或反向代理(如 Nginx)设置的 timeout,只管 HTTP 连接建立和请求头读取,对 handler 内部调用(比如 gRPC、数据库、第三方 HTTP API)完全无效。一旦下游服务卡住,整个 goroutine 就被锁死,QPS 会断崖下跌,还可能拖垮连接池。
必须用 context.WithTimeout 包裹 I/O 调用
所有阻塞型操作——http.DefaultClient.Do()、client.GetItem()、db.QueryRow()——都得从当前请求 context 派生一个带 deadline 的子 context,并显式传入。
- 正确写法:
ctx2, cancel := context.WithTimeout(ctx.Request().Context(), 3*time.Second) - 立刻 defer
cancel(),否则 context 泄漏会累积 goroutine - 别直接用
ctx.Request().Context()原始值——它没 deadline,超时控制形同虚设 - 若需透传 metadata(如 trace ID),用
metadata.AppendToOutgoingContext(ctx2, ...)注入,不是往原始 ctx 上加
中间件里怎么安全中断超时请求
超时中间件不是“加个 timeout 就完事”,关键在失败后立即终止链路,避免后续 handler 执行副作用(比如重复扣款、发通知)。
- 用
select+ctx2.Done()判断是否超时,而不是等 I/O 返回再检查 - 超时触发时,必须调
ctx.StatusCode(408)+ctx.WriteString("Request Timeout")+ctx.StopExecution() - 绝对不要在
ctx.StopExecution()后还调ctx.Next()——Iris 不阻止,但逻辑已错乱 - 如果 handler 里自己做了超时控制(比如用了
time.AfterFunc),中间件就别重复包一层,容易嵌套冲突
常见陷阱:Body 读取与 context 生命周期错位
很多超时中间件失败,是因为提前读了 ctx.Request().Body 却没重置,导致后续 handler 报 http: read on closed body;或者 context 已 cancel,但还在尝试读 Body。
- 读 Body 前先判断
ctx.Request().Context().Err() != nil,已 cancel 就跳过 - 若必须读(比如验签、日志),用
io.ReadAll()后立即ctx.Request().Body = io.NopCloser(bytes.NewReader(body))重置 - 禁用自动解析中间件(如
iris.Compression配合 JSON 解析)——它们可能偷偷消费 Body -
DisableBodyConsumptionOnUnmarshal: true是全局开关,慎用;局部控制更可靠
WithTimeout,而是每个 I/O 调用点都要主动适配、每个中间件都要检查 ctx.Err()、每个 Body 操作都要和 context 状态对齐——漏掉任意一环,超时就失效。











