Go语言标准库可实现支持断点续传的大文件下载:先HEAD获取总大小和Accept-Ranges,再根据本地文件大小设置Range请求头,用os.O_APPEND追加写入,配合io.Copy流式处理。

Go 语言实现文件下载,尤其是支持大文件和断点续传,核心在于合理使用 HTTP 协议的 Range 请求头、流式读写、以及本地文件的追加写入。不需要第三方库,标准库 net/http 和 os 就足够。
理解断点续传的关键机制
服务器需支持 Accept-Ranges: bytes 响应头,客户端才能发起分段请求。下载前先 HEAD 请求获取文件总大小和是否支持断点;若本地已有部分文件,用 Content-Range 和 Range 跳过已下载字节,从断点处继续。
- 用
http.Head()获取Content-Length和Accept-Ranges - 检查本地文件长度,若存在且小于总大小,说明可续传
- 发起 GET 请求时设置
Range: bytes=N-(N 为已下载字节数) - 响应状态码应为
206 Partial Content,而非200 OK
创建支持续传的下载函数
以下是一个精简可靠的下载函数示例,自动处理首次下载与续传逻辑:
func DownloadFile(url, filepath string) error {
// 1. 先 HEAD 获取文件信息
resp, err := http.Head(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HEAD failed: %s", resp.Status)
}
totalSize, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
if resp.Header.Get("Accept-Ranges") != "bytes" {
return fmt.Errorf("server does not support range requests")
}
// 2. 检查本地文件
var startOffset int64 = 0
if fi, err := os.Stat(filepath); err == nil {
startOffset = fi.Size()
if startOffset == totalSize {
return nil // 已完整下载
}
if startOffset > totalSize {
return fmt.Errorf("local file larger than remote")
}
}
// 3. 发起带 Range 的 GET 请求
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", startOffset))
resp, err = client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusPartialContent {
return fmt.Errorf("expected 206, got %s", resp.Status)
}
// 4. 打开文件:首次创建,续传则追加
flag := os.O_CREATE | os.O_WRONLY
if startOffset > 0 {
flag |= os.O_APPEND
}
f, err := os.OpenFile(filepath, flag, 0644)
if err != nil {
return err
}
defer f.Close()
// 5. 流式写入(避免内存爆满)
_, err = io.Copy(f, resp.Body)
return err
}
增强实用性的小技巧
真实场景中建议补充以下能力,不增加复杂度但显著提升健壮性:
立即学习“go语言免费学习笔记(深入)”;
-
进度回调:在
io.Copy中用io.TeeReader或自定义io.Reader统计已读字节数,定期通知调用方 - 重试机制:网络中断时捕获错误,等待后重试(最多 3 次),注意重试前重新 HEAD 确认服务状态
-
临时文件 + 原子重命名:下载写入
filepath + ".tmp",完成后再os.Rename,防止中断导致脏文件 -
限速与超时:给
http.Client设置Timeout和Transport的ResponseHeaderTimeout,避免卡死
使用示例与注意事项
调用方式简单:
err := DownloadFile("https://example.com/large.zip", "./large.zip")
if err != nil {
log.Fatal(err)
}
注意点:
- 某些 CDN 或静态托管(如 GitHub Releases)默认支持
Range;但部分 Nginx/Apache 需显式开启accept_ranges on; - Windows 下注意路径分隔符,建议用
path/filepath构造文件名 - 并发下载多个文件时,控制 goroutine 数量,避免耗尽连接或触发服务端限流










