timeouthandler 是包装器而非中间件,它通过独立 goroutine 监控超时并中断响应写入,但不终止原 handler 的执行;需手动结合 context.context 控制内部阻塞操作。

TimeoutHandler 本质是包装器,不是中间件
net/http.TimeoutHandler 的作用是给一个 http.Handler 加上整体请求超时控制,它会启动独立的 goroutine 监控时间,并在超时后中断响应写入。它不修改原 handler 的逻辑,也不参与中间件链式调用——你不能把它和 http.HandlerFunc 直接拼成链式中间件(比如 middleware(next) 那种),它只接受 http.Handler,返回一个新的 http.Handler。
常见错误是试图这样写:
http.Handle("/api", timeoutMiddleware(http.HandlerFunc(myHandler))) // ❌ timeoutMiddleware 不存在
正确做法是直接包装并注册:
- 确保你的 handler 是
http.Handler类型(http.HandlerFunc满足该接口) - 传入超时时间(
time.Duration),注意单位别写错,比如30 * time.Second而不是30 - 第三个参数是超时响应体,类型为
string,会被写入ResponseWriter,且 Content-Length 固定为其字节长度
超时触发后无法再写入响应,但 handler 可能仍在运行
这是最容易被忽略的点:TimeoutHandler 超时后会关闭底层 ResponseWriter 的写通道(内部用 io.ErrClosedPipe 模拟中断),但原 handler 的 goroutine 不会自动被取消或停止。如果你的 handler 里有数据库查询、HTTP 调用或循环处理,它们会继续执行,可能造成资源泄漏或重复副作用。
解决方法只有手动配合 context.Context:
- 不要依赖
TimeoutHandler自动终止业务逻辑 - 在 handler 内部显式接收
context.Context(例如从r.Context()获取) - 所有阻塞操作(如
db.QueryContext、http.Client.Do)必须传入该 context -
TimeoutHandler本身不会修改 request 的 context,但它会在超时后让r.Context().Done()关闭
示例片段:
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <h3>超时响应体不支持动态状态码或 Header</h3><p><code>TimeoutHandler</code> 写超时响应时,固定使用 <code>http.StatusServiceUnavailable</code>(503),且不允许设置自定义 Header 或状态码。它调用的是 <code>w.WriteHeader(http.StatusServiceUnavailable)</code> 后直接写入你传入的字符串。</p><p>如果你需要返回 408(Request Timeout)、自定义 JSON 错误、或添加 <code>X-Retry-After</code> 等 header,<code>TimeoutHandler</code> 无能为力。此时必须自己实现超时控制,例如:</p>
- 用
http.NewServeMux+ 自定义 wrapper,包装ResponseWriter并拦截WriteHeader - 用
context.WithTimeout包裹 handler 逻辑,在顶层判断ctx.Err() == context.DeadlineExceeded,再手动写响应 - 避免用
TimeoutHandler,改用反向代理类封装(如httputil.ReverseProxy自带超时)
与 ServeMux 和 Server 的超时字段有本质区别
http.Server 的 ReadTimeout、WriteTimeout、IdleTimeout 控制的是连接生命周期,不是单个 handler 执行时间。TimeoutHandler 控制的是 handler 函数体执行耗时,两者不冲突但职责不同。
典型误用是同时设了 Server.WriteTimeout = 5 * time.Second 又用 TimeoutHandler(..., 10 * time.Second, ...),结果后者永远没机会触发——连接在 5 秒后就被底层 TCP 连接强制断开了。
- 若要精细控制 handler 执行,只用
TimeoutHandler,并确保Server级超时 > handler 超时 -
ReadTimeout影响请求头/体读取,对流式上传尤其敏感;WriteTimeout影响响应写出,对长轮询或大文件下载关键 -
TimeoutHandler对 streaming handler(如text/event-stream)基本无效,因为只要持续写入,它就不会超时
真正难处理的,是 handler 里混用了阻塞 I/O 和 context 意识缺失——这时候加再多层超时也没用。











