最简方案是用http.Get发起请求,检查StatusCode后用io.ReadAll读取响应体再通过os.WriteFile写入,仅适用于几MB内小文件;必须defer关闭resp.Body,避免连接泄漏,且不可用于大文件以防OOM。

用 http.Get 下载小文件最简方案
直接发起 HTTP GET 请求,读取响应体并写入本地文件,适合几 MB 以内的文件。关键不是“能不能下”,而是“别卡死、别丢字节、别忽略错误”。
-
http.Get返回的resp.Body必须用defer resp.Body.Close()关闭,否则连接不释放,跑几次就too many open files - 别用
io.Copy直接怼到*os.File—— 要先检查resp.StatusCode,404 或 500 时io.Copy照常写空文件或错误内容 - 写文件前用
os.Create,别用os.OpenFile(..., os.O_CREATE|os.O_WRONLY)漏掉os.O_TRUNC,否则续传会叠加垃圾数据
resp, err := http.Get("https://example.com/file.zip")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatalf("HTTP %d: %s", resp.StatusCode, resp.Status)
}
out, err := os.Create("file.zip")
if err != nil {
log.Fatal(err)
}
defer out.Close()
io.Copy(out, resp.Body)
大文件下载必须加进度与超时控制
超过 10MB 就得考虑断网、慢速、服务端中断。默认的 http.DefaultClient 没有超时,io.Copy 也不反馈进度 —— 这俩问题不处理,用户就只能干等或杀进程。
- 用自定义
http.Client设置Timeout和Transport的IdleConnTimeout,防止卡在复用连接上 - 进度要用
io.TeeReader+ 计数器,别自己循环Read—— 容易错位、漏字节、没对齐 chunk - 下载中途出错(如网络断开),
resp.Body已部分读取,但文件已存在,得先os.Remove再重试,否则下次写是追加
client := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
IdleConnTimeout: 10 * time.Second,
},
}
resp, err := client.Get("https://big.file/1gb.bin")
// ... 检查 status code ...
f, _ := os.Create("1gb.bin")
defer f.Close()
var written int64
writer := io.MultiWriter(f, os.Stdout) // 示例:同时写文件和打印进度
n, err := io.Copy(writer, io.TeeReader(resp.Body, &written))
带请求头(如 Authorization)的下载怎么写
很多 API 文件资源需要 Authorization 或 User-Agent,用 http.Get 搞不定 —— 它不支持设 header,必须用 http.NewRequest + client.Do。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
http.NewRequest第二个参数是 URL 字符串,不是*url.URL;第三个参数必须传nil(GET 不需要 body),传错类型会 panic - header 要在
req.Header.Set后再调client.Do(req),顺序反了 header 就丢了 - 某些服务校验
User-Agent,空值会被 403,设成"Go-http-client/1.1"不够,得显式覆盖
req, _ := http.NewRequest("GET", "https://api.example.com/download?id=123", nil)
req.Header.Set("Authorization", "Bearer abc123")
req.Header.Set("User-Agent", "my-downloader/1.0")
resp, err := http.DefaultClient.Do(req)
并发下载多个 URL 的坑在哪
并发本身不难,难在错误隔离、限速、磁盘 IO 打满。开 10 个 goroutine 一起 http.Get,可能瞬间打爆目标服务器,也可能把本地 SSD 写挂。
立即学习“go语言免费学习笔记(深入)”;
- 别用无缓冲 channel 控制并发 ——
for range urls { go download(...) }会直接起一堆 goroutine,没 throttle - 每个下载任务要独立处理
resp.Body.Close(),共用一个http.Client没问题,但别共用resp.Body - 写文件时注意路径冲突:多个 URL 含相同文件名(如都叫
download),得靠filepath.Base+ 时间戳或哈希防覆盖
真正容易被忽略的是 DNS 缓存和连接复用 —— 默认 transport 对同一 host 复用连接,但并发高时可能触发 net/http: request canceled (Client.Timeout exceeded while awaiting headers),这时得调小 MaxIdleConnsPerHost 或关掉 keep-alive。

















