go标准库不支持断点续传开箱即用,必须手动处理range请求头、响应状态码校验(仅206可续)、文件偏移写入(用seek而非o_append);判断服务端支持需实测head请求并验证accept-ranges: bytes与206响应,写入须原子更新元数据防虚高大小导致错位。

Go 标准库不支持断点续传开箱即用,必须手动处理 Range 请求头、响应状态码校验、文件偏移写入三件事;缺一不可,否则看似在“续”,实则覆盖、错位或静默失败。
怎么判断服务端支不支持断点续传
不能只看文档或猜,得实测。最稳妥方式是先发一个 HEAD 请求:
- 检查
resp.Header.Get("Accept-Ranges") == "bytes"—— 这是服务端明确声明支持的信号 - 同时读取
resp.Header.Get("Content-Length"),转成int64作为总大小基准 - 若返回
405 Method Not Allowed,可 fallback 到GET+ 立即resp.Body.Close(),再读 header(部分 CDN 对 HEAD 不友好) - 若
Accept-Ranges是"none"或空,或返回416 Requested Range Not Satisfiable,说明硬续传会失败,应走全量路径
怎么构造和发送带 Range 的请求
别直接用 http.Get,必须手建 http.Request 并显式设头:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 先用
os.Stat(localPath)拿到已下载字节数offset - 仅当
offset > 0时才设置:req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset)) - 格式必须严格为
bytes=12345-,不能多空格、不能写成bytes=12345-12345(那是单字节) - 发完请求后,立刻检查
resp.StatusCode:只有206才能续;200表示服务端忽略Range,此时继续写等于重复写入
怎么把数据写到文件正确位置而不覆盖
用 os.O_APPEND 看似简单,但它是“追加到当前末尾”,不是“从 offset 开始写”——一旦多个 goroutine 同时写、或写入中途崩溃,文件末尾位置就不可信。
- 推荐统一用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)打开 - 调用
f.Seek(offset, io.SeekStart)显式定位 - 再用
io.CopyN(f, resp.Body, expectedSize)(而非io.Copy),防止服务端多发数据导致越界写入 - 若不确定
expectedSize,可结合Content-Range头解析:bytes 12345-67890/100000→ 当前段长 =67890 - 12345 + 1
为什么下载中断后不能只靠文件大小恢复
磁盘缓存未刷盘、写入一半 panic、IO 错误但没报错,都可能导致 os.Stat().Size() 返回“虚高”值——比如显示已下 99%,实际最后 1% 根本没落盘。
- 真正可靠的方式是维护一个元数据文件(如
file.zip.meta),记录url、totalSize、etag、chunks数组(每项含start、end、done) - 每个 chunk 下载成功后,原子更新 meta:先写
.tmp文件,再os.Rename()替换 - 启动时优先加载 meta;若解析失败或
etag不匹配(服务端文件已更新),就清空临时文件重来
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










