Go标准库net/http可下载文件,但需用自定义http.Client控制超时、重试与重定向,边读边写防OOM,校验状态码,支持断点续传,并安全提取文件名。
Go 标准库 net/http 完全能胜任文件下载,但直接用 http.Get 容易内存爆满或中断后无法续传——关键在控制流、响应体处理和错误恢复。
用 http.Client 发起带超时的下载请求
硬写 http.Get 会丢失连接控制权,超时、重试、重定向都难干预。必须用自定义 http.Client:
-
Timeout和Transport的IdleConnTimeout都要设,否则小文件没事,大文件卡住就僵死 - 下载大文件时建议关闭重定向(
CheckRedirect返回http.ErrUseLastResponse),避免跳转后丢掉原始Content-Length - 加
User-Agent头,部分服务(如 GitHub Releases)会拦截无 UA 的请求
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
IdleConnTimeout: 60 * time.Second,
},
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "golang-downloader/1.0")
resp, err := client.Do(req)
边读边写避免 OOM:用 io.Copy 而非 io.ReadAll
调 io.ReadAll(resp.Body) 会把整个响应体加载进内存,下载 1GB 文件就占 1GB RAM——生产环境绝对禁止。
- 直接用
io.Copy把resp.Body流式写入本地*os.File,内存占用恒定在几 KB - 务必检查
resp.StatusCode是否为200,否则io.Copy会静默把 404 HTML 写进文件 - 若需校验完整性,可在
io.Copy时用io.TeeReader同时计算sha256,不额外遍历文件
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
f, _ := os.Create("out.zip")
defer f.Close()
_, err := io.Copy(f, resp.Body) // 不会吃光内存
支持断点续传:用 Range 请求头 + os.OpenFile 追加模式
网络抖动或用户中断后,重新下载整个文件既慢又费流量。HTTP 断点续传依赖服务端支持 Accept-Ranges: bytes,客户端需: