如何使用Golang实现文件下载功能_Golang文件处理与下载优化

2次阅读

go标准库net/http可下载文件,但需用自定义http.Client控制超时、重试与重定向,边读边写防OOM,校验状态码,支持断点续传,并安全提取文件名。

如何使用Golang实现文件下载功能_Golang文件处理与下载优化

Go 标准库 net/http 完全能胜任文件下载,但直接用 http.Get 容易内存爆满或中断后无法续传——关键在控制流、响应体处理和错误恢复。

http.Client 发起带超时的下载请求

硬写 http.Get 会丢失连接控制权,超时、重试、重定向都难干预。必须用自定义 http.Client

  • TimeoutTransportIdleConnTimeout 都要设,否则小文件没事,大文件卡住就僵死
  • 下载大文件时建议关闭重定向(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.Copyresp.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,客户端需:

立即学习go语言免费学习笔记(深入)”;

  • HEAD 请求获取 Content-Length 和是否支持 Range
  • 检查本地文件是否存在且非空,用 os.Stat 获取已下载字节done
  • 发起 GET 时加请求头:Range: bytes=done-,并用 os.OpenFile(..., os.O_WRONLY|os.O_appEND) 打开文件
  • 注意:服务端返回状态码206 Partial Content,不是 200

不校验 Content-Range 响应头就写入,可能造成文件错位——比如服务端实际从 1000 字节开始发,但你从 800 字节处追加,后半截就乱了。

文件名提取别信 Content-Disposition,优先 fallback 到 URL 路径

Content-Disposition: attachment; filename="foo.pdf" 看似可靠,但大量服务(尤其是 cdn 或反向代理)根本不返回该头,或返回非法字符(如中文未编码、含路径分隔符)。

  • 先尝试解析 Content-Disposition,用 mime.ParseMediaType 提取 filename*(RFC 5987)或 filename(RFC 2231)
  • 失败或为空时,从 URL 路径取最后一段(path.Base(url.Path)),再用 strings.TrimSuffix 去掉查询参数
  • 最终文件名必须过滤掉 / : * ? " | 等危险字符,windows 和某些文件系统不认

没做路径清洗就直接 os.Create(filename),遇到 ../../etc/passwd 这类构造名可能覆盖系统文件——哪怕只是测试也要防住。

text=ZqhQzanResources