http.Get 直接下载常静默失败,因无超时、不校验状态码、缺 User-Agent 被 403 拦截、未流式处理致 OOM;应显式构造 http.Client,设 Timeout、User-Agent 和 Accept,检查 StatusCode,用 io.Copy 流式落盘并清理错误文件。

直接用 http.Get 拉远程文件,大概率失败——不是代码写错了,是它没超时、不校验状态码、不设请求头、不处理重定向,连 403 都当成功。
为什么 http.Get 下载常静默失败
服务端(GitHub raw、Cloudflare、Vercel、Netlify)普遍拦截无 User-Agent 的请求,返回 403 或 HTML 登录页;http.Get 不检查响应状态,直接把错误页写进本地文件,你还以为“下载好了”。更糟的是:大文件会因未流式处理而触发 OOM,io.ReadAll 全读进内存是典型反模式。
- 必须显式构造
http.Client并设Timeout(建议 ≤30s) - 手动设置
req.Header.Set("User-Agent", "Mozilla/5.0") - 二进制文件加
req.Header.Set("Accept", "application/octet-stream") -
resp.StatusCode必须检查,只接受200或明确支持的302(需自行重放) -
defer resp.Body.Close()要放在状态码检查之后,否则 4xx/5xx 响应体没关干净,复用连接会出问题
如何安全流式下载并落盘
核心是避免内存膨胀和文件损坏。用 io.Copy 替代 io.ReadAll,它内部用 32KB 缓冲区逐块搬运,既省内存又防丢字节。
- 用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)创建文件,确保覆盖写入 - 若路径含子目录,先调
os.MkdirAll(filepath.Dir(path), 0755) -
io.Copy(dst, resp.Body)返回非 nil error 时,文件已存在但不完整——必须手动os.Remove(path)清理,标准库不擦屁股 - 不要忽略
resp.ContentLength,它可用于预分配或进度估算(但非绝对可靠)
断点续传必须先 HEAD 验证
不是所有 HTTP 服务都支持 Range,强行发会返回 200 + 全量内容,导致重复写入、文件错位。必须先 HEAD 请求确认服务端是否返回 Accept-Ranges: bytes。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 用
os.Stat(path).Size()获取已下载字节数 - 构造
GET请求时设req.Header.Set("Range", fmt.Sprintf("bytes=%d-", downloaded)) - 仅当
resp.StatusCode == 206时才用os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0)追加写入 - 若返回 200,说明服务端不支持断点续传,应删除已有文件后重新完整下载
不同协议不能混用 net/http
http.Get("ftp://...") 会 panic:no such host,因为 net/http 只识别 http/https scheme。FTP 和 SFTP 必须换专用库。
- FTP:用
github.com/jlaffaye/ftp,支持主动/被动模式、认证、LIST/PASV 流程 - SFTP:用
github.com/pkg/sftp,基于 SSH 连接,需配ssh.ClientConfig - 别试图用
exec.Command("curl", ...)绕过——失去 Go 的并发控制与错误上下文,且跨平台不可靠
真正难的不是写几行下载代码,而是判断什么时候该删临时文件、什么时候该重试、什么时候该放弃;Range 头值跟本地文件大小对不上,或者服务端悄悄关闭了 Accept-Ranges,这些细节一漏,同步就卡死在半途。

















