idletimeout通过setreaddeadline动态设置读截止时间触发:每次新请求到达时重置为当前时间加idletimeout,超时后conn.read()返回io.eof或net.errclosed,服务端静默关闭连接。

IdleTimeout 是怎么被触发的
它不靠定时器轮询,也不在每次请求后“倒计时”,而是依赖 Go net.Conn 底层的 SetReadDeadline 动态设置。每次有新请求到达(或连接刚建立),http.server 会为该连接重置一次读截止时间:当前时间 + IdleTimeout。只要连接上没数据可读、也没新请求进来,Deadline 就不会刷新;一旦超时,下一次 conn.Read() 就返回 io.EOF 或 net.ErrClosed,服务端随即关闭连接。
关键源码位置在 server.go 的 serve 方法里
Go 标准库中实际逻辑集中在 net/http/server.go 的 (*conn).serve() 和 (*conn).readRequest() 中:
-
(*conn).readRequest()开头会调用c.setState(c.rwc, StateActive),紧接着调用c.setReadDeadline()—— 这个setReadDeadline就是把time.Now().Add(s.IdleTimeout)写进底层 conn - 如果
IdleTimeout == 0,则跳过设置 deadline,连接永远不因空闲被关 - 若后续在等待新请求时
conn.Read()返回超时错误,serve()会直接break循环并调用c.close()
注意:这个过程完全静默,不打日志、不调 handler、不走任何中间件 —— 所以你只会在客户端看到 connection reset 或 broken pipe,服务端日志里什么痕迹都没有。
为什么没看到 “idle timeout” 相关 error 日志
因为 Go 的实现把它当作普通 I/O 错误处理,而不是业务级超时事件。相关错误被归入 net.Error,且 Timeout() 方法返回 true,但标准库没做特殊分类或打点。你无法通过 log.SetFlags(log.Lshortfile) 捕获到哪条连接因 idle 被关 —— 它和客户端主动断连、网络中断在代码路径上几乎一致。
- 调试时只能靠
netstat -an | grep :8080 | grep ESTABLISHED | wc -l观察连接数是否阶梯式下降 - 或用
strace -p $(pidof yourserver) -e trace=epoll_wait,read,close看底层系统调用行为 - 别指望
http.Server.ErrorLog输出 idle 断连记录 —— 它真不记
IdleTimeout 不等于 KeepAliveTimeout,且已被彻底移除
KeepAliveTimeout 在 Go 1.8 已被标记为 deprecated,并在 Go 1.22+ 中彻底删除。所有对它的赋值(比如 srv.KeepAliveTimeout = 60 * time.Second)在新版本编译期就报错。现在唯一合法字段就是 IdleTimeout,语义更精确:只管“无读写活动”的空闲期,不管请求处理本身。
- 如果你在旧项目里看到
KeepAliveTimeout,必须替换为IdleTimeout - 如果同时设置了两者,Go 1.20+ 会忽略
KeepAliveTimeout,且不警告 - 这个字段没有回调钩子、不可扩展、不能 hook —— 它就是个纯 deadline 控制开关
真正难搞的不是“怎么设”,而是“设了之后上下游谁先断”。服务端设了 60s,但 nginx upstream keepalive_timeout 是 75s,或者 Go client 的 IdleConnTimeout 是 90s,结果就是一方静默关了连接,另一方还在往已关闭的 fd 上写,错误才真正爆发。











