bigywater.tech
Go · 89

HTTP 超时:Server 与 Client 各有哪几种超时

Go 的 HTTP 超时分散在 Server、Transport、Client、context 好几处,每个管的阶段不同。这篇逐个用真实运行验证它们各自管什么、不管什么,尤其是 WriteTimeout 和 ResponseHeaderTimeout 这两个最容易被误解的。

3001 ms vs 200 ms
不设与设了 ReadHeaderTimeout 时,半个请求头占着连接的时间
502 ms,0 字节
WriteTimeout 200 ms 而 handler 要 500 ms:连接被关,响应没送达,handler 跑到了结束
3 vs 10 字节
服务端慢速发 body 时,Client.Timeout 与 ResponseHeaderTimeout 下客户端读到的字节数

ReadHeaderTimeout

实测 · 本机执行输出
  ReadHeaderTimeout = 0s     客户端只发了半个请求头就不再发:3001 ms后——3 秒内服务器没有关闭连接
  ReadHeaderTimeout = 200ms  客户端只发了半个请求头就不再发:200 ms后——服务器关闭了连接,客户端收到 0 字节

不设 ReadHeaderTimeout,客户端发半个请求头后就不动,连接可以一直占着(实验里等了 3 秒还在);设 200 ms 后 200 ms 就被关。对外服务必须设置。

WriteTimeout 的坑

实测 · 本机执行输出
  handler 睡了 500 ms,写响应时 err = <nil>
  WriteTimeout = 200 ms,handler 要 500 ms:客户端在 502 ms 后看到:服务器关闭了连接,客户端收到 0 字节

handler 睡 500 ms,WriteTimeout 只有 200 ms:handler 并没有被取消,它写响应时也没报错(写进了缓冲),但客户端 502 ms 后只看到连接被关,0 字节。

IdleTimeout

实测 · 本机执行输出
  IdleTimeout = 300 ms,一个请求完成后连接保持空闲:301 ms 后服务器关闭了连接(客户端读到 EOF)

IdleTimeout 控制 keep-alive 连接空闲多久被回收:实验里设 300 ms,301 ms 后服务器关闭了连接。

http.TimeoutHandler

实测 · 本机执行输出
  http.TimeoutHandler(100 ms),handler 要 500 ms:101 ms 后客户端收到 503 Service Unavailable,body "服务超时"(err=<nil>)
  handler 里 r.Context() 的状态:context deadline exceeded

想让慢 handler 能提前结束,用 http.TimeoutHandler:100 ms 后客户端得到 503 和自定义文字,handler 的 r.Context() 也变成 deadline exceeded——但 handler 要自己监听才会收手。

客户端超时的范围

实测 · 本机执行输出
  Client.Timeout=300ms                 响应头立刻到;读 body 301 ms 后结束,读到 3 字节,err = context deadline exceeded (Client.Timeout or context cancellation while reading body)
  Transport.ResponseHeaderTimeout=300ms 响应头立刻到;读 body 1012 ms 后结束,读到 10 字节,err = <nil>
  ctx WithTimeout(300ms)               响应头立刻到;读 body 301 ms 后结束,读到 3 字节,err = context deadline exceeded

服务端先发响应头、再每 100 ms 发 1 字节。Client.Timeout 与 ctx 超时都会截断读 body(读到 3 字节);Transport.ResponseHeaderTimeout 只管等响应头,body 慢也不会超时(读完 10 字节用了约 1 秒)。想限制整个请求的时长用前两者。

连接超时

实测 · 本机执行输出
  Dialer.Timeout = 300 ms,连接 10.255.255.1:80:301 ms 后返回:dial tcp 10.255.255.1:80: i/o timeout

往不可达的地址连接,Dialer.Timeout 设 300 ms 就在 301 ms 后返回 i/o timeout。Transport 的 DialContext 里要自己设这个超时。

文档怎么说

实测 · 本机执行输出
  ReadHeaderTimeout is the amount of time allowed to read request headers. The connection's read deadline is reset after reading the headers and the Handler can decide what is considered too slow for the body. If zero, the value of
  WriteTimeout is the maximum duration before timing out writes of the response. It is reset whenever a new request's header is read. Like ReadTimeout, it does not let Handlers make decisions on a per-request basis. A zero or negative value means there will be no timeout.
  IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. If zero, the value of ReadTimeout is used. If negative, or if zero and ReadTimeout is zero or negative, there is no timeout.

ReadHeaderTimeout 管读请求头,读完后读截止时间重置;WriteTimeout 每读到新请求头就重置,不让 Handler 逐请求决定;IdleTimeout 管 keep-alive 下等下一个请求,为零时借用 ReadTimeout。

分步演示

HTTP 超时
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。服务端用 http.Server 的各个超时字段,客户端用原始 TCP(半个请求头)或 net/http Client;时间用 time.Since 测;连接超时用不可达地址 10.255.255.1(这台机器网络环境下得到 i/o timeout,不同网络可能是立即失败);文档摘自 go doc net/http.Server
    • 没有验证:TLS 握手超时、ReadTimeout 与读 body 的关系、HTTP/2 下这些超时的行为、http.Server 的 Shutdown 超时、Expect: 100-continue 超时、10.255.255.1 在其它网络中的行为
    京ICP备2026067502号-1京公网安备11010502063487号