http.get 返回403或空内容主因是缺少user-agent头被服务端拦截;需手动设置user-agent及accept头,检查statuscode,用io.copy流式下载并处理错误,断点续传前需head验证accept-ranges支持。

用 http.Get 下载文件时为啥返回 403 或空内容?
很多同学一上来就写 http.Get,结果拿到 403 Forbidden 或者响应体为空——不是代码写错了,是服务端认出你没带 User-Agent,直接拒了。Golang 的 http.Client 默认不发任何 header,而多数 CDN、Nginx 或 GitHub raw 链接都会拦截这种“裸请求”。
实操建议:
- 必须手动设置
req.Header.Set("User-Agent", "Mozilla/5.0"),哪怕只是模拟浏览器 - 如果目标是 GitHub raw 文件,还得加
Accept: application/octet-stream,否则可能返回 HTML 页面而不是二进制流 - 别跳过
resp.StatusCode检查:200 是成功,302 要手动重定向(http.Client默认不自动跟随重定向下载大文件,容易卡住或下错)
io.Copy 和 io.CopyN 选哪个?为什么不能直接 resp.Body.Read?
直接读 resp.Body 容易出问题:没处理好缓冲区大小,要么内存爆掉(大文件全 load 进内存),要么丢数据(没读完就 close)。Go 标准库推荐用 io.Copy,它内部用 32KB 缓冲区流式写入,安全又省心。
但注意两个坑:
-
io.Copy会一直读到EOF,适合完整下载;如果只想取前 1MB 做校验,改用io.CopyN(dst, src, 1024*1024) - 别忘了在
Copy前创建文件句柄,并用os.O_CREATE | os.O_WRONLY | os.O_TRUNC打开,否则可能覆盖失败或权限报错 - 下载中途出错(如网络断开),
io.Copy返回非 nil error,此时文件已存在但不完整——得自己删掉再重试,标准库不帮你擦屁股
怎么支持断点续传?Range header 怎么设才不被忽略?
服务端是否支持断点续传,看它是否返回 Accept-Ranges: bytes。不是所有 HTTP 服务都开这个,比如大多数静态托管(Vercel、Netlify)默认关掉,强行发 Range 会返回 200 + 全量内容,而不是 206 Partial Content。
实操要点:
- 先 HEAD 请求检查响应头:
resp.Header.Get("Accept-Ranges") == "bytes" - 如果支持,再 GET 时加
req.Header.Set("Range", "bytes=1024-")(从第 1024 字节开始续) - 注意:
Range值必须和本地文件当前长度一致,否则续传错位;建议用os.Stat获取已下载大小再构造 - 收到 206 响应后,用
os.OpenFile(..., os.O_WRONLY|os.O_APPEND)打开文件,避免覆盖
下载大文件时内存不涨,但 CPU 占用高,怎么回事?
看起来没加载全文进内存,但 CPU 高,大概率是日志或监控逻辑在高频打点——比如每 KB 就调用一次 log.Printf 或更新进度条。字符串拼接、格式化、锁竞争,在循环里放大后非常吃 CPU。
优化方向很具体:
- 把进度回调频率降下来:不是每次
io.Copy内部读都触发,而是累计满 1MB 再通知一次 - 避免在拷贝循环里做任何非 IO 操作;
io.Copy本身不回调,你要自己包一层io.Reader做计数,但别在里面 fmt 或 log - 如果用了
gzip响应(Content-Encoding: gzip),解压本身也耗 CPU,确认服务端是否真需要压缩——下载静态二进制文件时,通常该关掉 gzip
断点续传的兼容性比想象中差,很多 CDN 和反向代理对 Range 处理不一致;真要稳定,不如用带重试+校验的封装,比如 github.com/hashicorp/go-retryablehttp 配合手动 Range 控制。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











