Go模拟高延迟弱网的核心是用DelayRoundTripper在RoundTrip中注入time.Sleep,卡在“发完请求、等响应”之间,而非改handler或用httptest;需确保延迟值大于Transport各超时阈值,并用httptrace验证延迟发生在GotFirstResponseByte前。

直接用 http.Client 的超时参数只能验证“超时是否触发”,但无法复现“请求发得出去、响应迟迟不来”的真实弱网行为。要模拟高延迟弱网,必须让数据在传输路径上被可控地卡住,而不是让客户端自己放弃。
用 DelayRoundTripper 在 HTTP 请求链路中注入延迟
这是最轻量、不依赖外部服务、且能精准控制每轮请求延迟的方式。关键不是改 handler 或 sleep 整个 goroutine,而是拦截底层网络调用。
- 实现
http.RoundTripper接口,在RoundTrip方法开头调用time.Sleep—— 这会卡在“发完请求、等响应”之间,符合真实延迟语义 - 延迟值建议从
req.Context().Value()读取,比如用context.WithValue(ctx, delayKey, 800)传入毫秒数,方便单测中 per-request 控制 - 别直接包装
http.DefaultTransport后全局替换,否则会影响其他测试;应为每个测试 client 单独配置新 transport - HTTP/2 场景下需透传原
http2.Transport,否则可能 panic 或跳过延迟
用 Toxiproxy 模拟下游响应延迟(推荐集成测试)
Toxiproxy 是独立进程,适合跨服务、多协议(HTTP/Redis/gRPC)的端到端弱网验证。Go 侧只负责调 API 控制它,不侵入业务逻辑。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 延迟 toxic 必须加在
downstream方向:因为downstream指“代理 → 客户端”,即响应返回路径;加错方向会导致请求发不出去,而非响应慢 - 创建 proxy 时,
listen是测试客户端访问的地址(如localhost:8081),upstream是真实后端地址(如localhost:8000) - 用
proxy.AddToxic添加latencytoxic 时,latency单位是毫秒,jitter是可选波动范围(如jitter: 200表示 ±200ms) - 重复添加同名 toxic 会报错;更新参数要用
proxy.UpdateToxic,或先proxy.RemoveToxic
为什么 httptest.NewServer 不适合模拟弱网
httptest.NewServer 返回的是内存级 handler 封装,绕过了 TCP/IP 栈和网络 I/O,所有调用都是同步函数调用。你在 handler 里 time.Sleep(2 * time.Second),测出来的是“业务逻辑卡顿”,不是“网络延迟”。
- 它适合验证 JSON 结构、状态码、中间件顺序等协议层逻辑
- 要测真实建连耗时、首字节延迟、连接中断重试等,必须走真实 socket:用
net.Listen("tcp", ":0")起服务,再让 client 连这个地址 - 若用
httptest.NewServer+time.Sleep,你会发现httptrace里ConnectDone和GotConn时间戳几乎重合,根本看不到网络阶段耗时
容易被忽略的关键点:延迟必须大于 Transport 默认超时
即使你成功注入了 1.5s 延迟,如果 http.Transport.ResponseHeaderTimeout = 1s,那 client 会在收到响应头前就报错 net/http: request canceled (Client.Timeout or context cancellation),根本等不到延迟生效。
- 测试前务必检查 client 所有超时字段:
Timeout、Transport.DialContext.Timeout、Transport.ResponseHeaderTimeout、Transport.IdleConnTimeout - 延迟值建议设为超时阈值的 1.2–2 倍,例如想验证 2s 超时逻辑,延迟至少设 2400ms
- 用
httptrace打点确认延迟确实发生在GotFirstResponseByte之前,而不是被更早的超时截断
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










