
本文揭示了 Go 程序中 HTTP 请求“看似发送两次”的典型误判原因——并非代码或 net/http 库缺陷,而是第三方安全软件(如 AVG)在进程启动时的拦截扫描行为导致终端重复触发,造成服务端收到重复请求的假象。
本文揭示了 go 程序中 http 请求“看似发送两次”的典型误判原因——并非代码或 net/http 库缺陷,而是第三方安全软件(如 avg)在进程启动时的拦截扫描行为导致终端重复触发,造成服务端收到重复请求的假象。
在 Go 开发中,当使用标准库 net/http 发起 POST 请求(例如调用 Firebase Cloud Messaging 或自建 API)时,若服务端日志显示同一请求被接收两次,开发者常本能地怀疑代码逻辑、连接复用(Keep-Alive)、重定向处理或客户端并发问题。但真实案例表明:问题根源可能完全在运行环境之外。
例如以下典型 HTTP POST 代码并无重复调用风险:
resp, err := http.Post("https://your-api.com/notify", "application/json", bytes.NewBuffer(payload))
if err != nil {
log.Fatal("Request failed:", err)
}
defer resp.Body.Close()
该代码仅执行一次 http.Post,且 net/http 默认不自动重试(除非显式配置 http.Client.CheckRedirect 或使用第三方重试中间件)。Go 标准库的 HTTP 客户端是线程安全且行为确定的——只要主流程未被重复执行,请求绝不会自发发送两次。
那么为何观察到“双请求”?关键线索在于:
✅ 服务端确收两条请求;
✅ 终端无重复输出、无 panic 或异常日志;
✅ 切换不同服务端(Firebase / 自建)现象一致;
❌ 代码中无循环、goroutine 误启、信号捕获或 main() 多次进入逻辑。
此时应转向运行时环境干扰排查。如原问题所述,AVG 防病毒软件在 go run program.go 执行瞬间弹出扫描提示框,并在用户按回车确认后才放行进程——而部分终端(尤其是 Windows CMD 或 PowerShell)在弹窗阻塞期间可能将首次回车同时传递给 AVG 和底层 Go 进程,导致 go run 被意外触发两次(即:第一次回车触发改杀毒扫描,第二次回车(或同一回车事件)触发程序实际运行;或 AVG 的钩子机制引发进程 fork 副本)。这种底层系统级干预不会在 Go 日志中留下痕迹,却会真实产生两个独立的 HTTP 连接。
✅ 验证方法:
- 临时禁用 AVG 实时防护,再次运行
go run,确认请求回归单次; - 改用
go build && ./program方式执行(绕过go run的临时编译与加载环节),观察是否仍复现; - 使用
tcpdump或 Wireshark 抓包,确认是否真有两条独立 TCP 连接发出(而非服务端响应重传)。
⚠️ 注意事项:
- 不要盲目修改
http.Client.Timeout或添加req.Header.Set("Connection", "close")——这无法解决环境层干扰; - 避免在调试阶段依赖终端回车行为,建议通过 IDE 运行或脚本启动;
- 生产部署时,务必在干净环境(如 Docker 容器)中验证网络行为,隔离杀毒软件等桌面级干扰源。
归根结底,这是一个经典的“非代码问题”:当现象违背直觉且排除所有语言层因素后,需将怀疑范围扩展至操作系统、安全软件、Shell 行为甚至硬件输入事件。保持对运行栈全链路的敬畏,是高效定位疑难问题的关键。










