Go标准库http.Client不支持断点续传,需手动实现:先HEAD验证Accept-Ranges和206状态码,再设Range头并用Seek定位写入,遇416需清空重下,禁用O_APPEND。

Go 标准库的 http.Client 本身不支持断点续传,但完全能手动实现——关键不是“能不能”,而是你是否校验了服务端响应、是否正确设置了 Range 头、是否在写入前调用了 Seek()。
怎么判断服务端是否真支持 Range
别只看文档或假设 CDN 支持。必须实测:
- 用
curl -I -H "Range: bytes=0-999" <url>发起 HEAD 请求 - 检查响应头中是否有
Accept-Ranges: bytes - 检查状态码是否为
206(不是200),且响应体长度是 1000 字节左右 - 如果返回
200或无Accept-Ranges,说明服务端忽略Range,强行续传会覆盖已有内容
如何安全构造并发送带 Range 的请求
错一个字符或漏一次校验,就可能写错位置或静默失败:
- 先用
os.Stat()获取本地文件大小,作为offset;若文件不存在,offset = 0 - 只在
offset > 0时设置头:req.Header.Set("Range", "bytes="+strconv.FormatInt(offset, 10)+"-") - 务必用
http.Client.Do()后立刻检查resp.StatusCode:只有206才可续传;200要报错退出,416需清空重下 - 不要依赖
Content-Range响应头来反推 offset —— 它可能被中间代理篡改;以你发的Range值为准
为什么写入文件时必须 Seek 而不能用 O_APPEND
O_APPEND 看似省事,但它只保证“追加到当前文件末尾”,而这个“末尾”可能已被 truncate 或被其他进程修改过:
立即学习“go语言免费学习笔记(深入)”;
- 打开文件要用
os.O_WRONLY | os.O_CREATE,**不用**O_APPEND - 调用
f.Seek(offset, io.SeekStart)显式定位,这是唯一能确保数据写入指定字节位置的方式 - 漏掉这一步,表面下载完成,实际所有新数据都叠在文件开头,哈希校验必失败
- 写入推荐用
io.CopyBuffer(f, resp.Body, buf),避免内存暴涨;buf可设为 32KB~1MB
遇到中断或 416 怎么恢复
断点续传不是“自动兜底”,而是要明确决策:
- 下载中被 cancel 或网络断开:直接退出,下次启动时仍从
os.Stat().Size()继续——只要文件没被破坏 - 收到
416 Range Not Satisfiable:大概率是服务端文件已被删/重置,此时应f.Truncate(0)清空本地文件,再以offset = 0全量重下 - 不建议自动 fallback 到全量下载:用户中断一次后,默默重下整份大文件,体验比失败还差
- 如需持久化 checkpoint(比如跨进程恢复),得额外存一个
.state文件记录 offset 和 ETag,否则仅靠文件大小不够可靠
最常被跳过的环节是:HEAD 验证 Accept-Ranges、状态码校验、Seek 定位。这三个点任一缺失,断点逻辑就形同虚设——表面跑通,实际每次都在覆盖重写。


















