go 的 http.client 本身不支持带宽限制,需通过 io.limitreader(下载限速)或包装 net.conn(全链路限速)实现;上传限速需包装 req.body;netutil.limitlistener 不适用于客户端。

Go 标准库的 http.Client 本身不提供带宽限制能力,必须借助底层连接控制或中间层包装来实现——这不是加个参数就能开的功能。
用 io.LimitReader 控制响应体读取速率
这是最轻量、最常用的方式,适用于你已发起请求、只关心“下载速度”的场景。它不阻塞连接建立或请求发送,只节制 response.Body.Read() 的吞吐。
- 对
http.Response.Body套一层io.LimitReader,传入每秒字节数(如1024 * 1024表示 1MB/s) - 注意:必须在每次
Read()前确保 reader 未被提前关闭;若用io.Copy,需包装成自定义io.Reader并在Read()内做限速休眠 - 无法限制请求体上传(
req.Body)或 TLS 握手/HTTP 头传输阶段的带宽
示例片段:
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
limitedBody := &rateLimitedReader{
Reader: resp.Body,
limiter: time.NewTicker(1 * time.Second),
bytesPerTick: 512 * 1024, // 512KB/s
}
io.Copy(dst, limitedBody)
用 net/http.Transport + 自定义 RoundTripper 实现连接级限速
要真正约束整个 HTTP 流(含请求头、请求体、响应头、响应体),需替换 http.Transport 的底层连接行为。标准库不支持,但可通过包装 net.Conn 实现。
立即学习“go语言免费学习笔记(深入)”;
- 核心是实现一个带速率限制的
net.Conn,重写Write()和Read()方法,内部用golang.org/x/time/rate.Limiter控制 - 将该 Conn 注入自定义
http.Transport.DialContext返回的连接中 - 注意:TLS 连接需在
tls.Conn封装之后再套限速层,否则会破坏握手流程 - 并发请求下,每个连接独立限速;若需全局带宽上限,limiter 必须共享且作用于所有连接的读/写路径
第三方库 golang.org/x/net/netutil 不适用,别踩坑
这个包里的 LimitListener 是为 net.Listener 设计的(服务端限流),对 HTTP 客户端完全无效。试图用它套 http.Client 会编译失败或静默失效。
- 常见误用:
http.ListenAndServe场景才用netutil.LimitListener - 客户端限速必须从
net.Conn或io.Reader/Writer层介入 - 社区有轻量封装库如
github.com/mccutchen/go-httpbin/httpbin(非主流),但生产环境建议手写可控逻辑
上传大文件时如何限速?重点在 req.Body
如果你用 os.Open 或 bytes.NewReader 作为请求体,限速点就在构造 req.Body 时——不能直接传原始文件句柄,得包装成带节流的 io.Reader。
- 不要:
req, _ := http.NewRequest("PUT", url, file) - 应该:
req, _ := http.NewRequest("PUT", url, &rateLimitedReader{Reader: file, ...}) - 上传限速和下载限速互不影响,需分别实现;若共用一个
rate.Limiter,注意设置足够大的桶容量避免突发阻塞 - 超时控制仍需单独配置
http.Client.Timeout或context.WithTimeout,限速不改变超时逻辑
真正的难点不在代码行数,而在于厘清「限的是哪一段数据流」:是 TCP 包?TLS 记录?HTTP 消息体?还是应用层解析后的字节?选错层级,限速就变成假动作。










