用http.head检查服务器是否支持断点续传,需发送head请求并验证响应头中是否存在accept-ranges: bytes;若缺失或返回405,则改用get加range: bytes=0-0探测是否返回206 partial content。

如何用 http.Head 检查服务器是否支持断点续传
断点续传的前提是服务端支持 Range 请求,否则客户端发了 Range 头也没用。最直接的办法是先发一个 HEAD 请求,看响应头里有没有 Accept-Ranges: bytes 或 Content-Range 字段。
注意:有些服务器(比如 Nginx 默认配置)对静态文件返回 Accept-Ranges: bytes,但对动态接口(如 PHP/Go 后端生成的下载链接)可能完全不返回该头,即使它实际支持 Range —— 这时得靠试错:GET 加 Range: bytes=0-0 看是否返回 206 Partial Content。
- 别只依赖
Accept-Ranges头,某些 CDN 或反向代理会过滤或伪造它 - 如果
HEAD返回405 Method Not Allowed,改用GET+ 小范围Range探测 - 部分服务(如 GitHub Releases)对
HEAD不返回Accept-Ranges,但实际支持Range,必须实测
用 io.Seek 和 os.O_APPEND 续写本地文件
本地文件已存在且有部分内容时,不能直接 os.Create 覆盖,也不能用 os.O_WRONLY | os.O_CREATE —— 这会导致从头写,覆盖已有数据。正确做法是打开文件并定位到末尾,再追加新数据。
关键点在于:先用 os.Stat 获取已有文件大小,作为后续请求的 Range 起始偏移;再用 os.OpenFile 以 os.O_WRONLY | os.O_APPEND 打开,但注意 O_APPEND 会强制写入末尾,无法控制偏移 —— 所以得先 Seek 到指定位置,再用 Write(不是 WriteAt,后者在 Windows 上可能出错)。
- 务必检查
os.Stat错误,文件可能被删除或权限变化 -
Seek后不要用WriteAt:它在某些文件系统(如 NTFS)上不保证原子性,且 Go 的os.File.WriteAt在 Windows 下可能返回ERROR_INVALID_PARAMETER - 写入前建议用
file.Sync()刷盘,避免断电导致文件头尾不一致
构造正确的 Range 请求头和处理 206 响应
HTTP 断点续传的核心是 Range 头格式必须合法,且客户端要能识别 206 Partial Content 并提取真实内容长度。常见错误是把 Range: bytes=100- 写成 Range: bytes=100-199 却没校验响应里的 Content-Range,结果写错位置。
示例:已下载 1024 字节,下次请求应设 Range: bytes=1024-;服务端若返回 Content-Range: bytes 1024-4095/8192,说明总长 8192,当前块含 3072 字节 —— 这个 3072 必须用于后续 Write 长度判断,不能只信 Content-Length(206 响应中它表示本块长度,不是总长)。
-
Range值末尾带短横线(1024-)更稳妥,避免计算结束偏移出错 - 收到
206后,必须解析Content-Range头确认起始/结束偏移,防止服务端忽略Range直接返回完整文件(此时是200,需降级处理) - 如果响应是
200而非206,说明服务端不支持断点,应清空已下载文件并重新全量下载
如何安全地合并分块下载与校验逻辑
真实场景中常需要多 goroutine 分块并发下载(比如把大文件切 4 段),但这和断点续传有冲突:每个 goroutine 都要独立维护自己的偏移、重试、写入位置,稍有不慎就会写错区域或覆盖。
更可行的做法是单 goroutine 顺序下载,靠 Range 头跳过已下载部分;若真要并发,必须用 sync.Mutex 或 chan int64 控制写入偏移,且每个 goroutine 的 Range 区间必须严格互斥、无重叠、无缝衔接。另外,MD5/SHA256 校验不能等全部下完再算 —— 应边写边更新哈希,否则断点后重启得重算整个文件。
- 并发写入时,
file.Write是线程安全的,但写入位置由调用方控制,必须自己同步偏移量 - 用
hash.Hash.Write接入io.MultiWriter可同时写文件+更新哈希,避免二次读取 - 断点续传的临时文件名建议加
.part后缀,下载完成后再os.Rename,防止程序崩溃留下脏文件
断点续传真正难的不是发 Range 头,而是状态持久化:已下载字节数、临时文件路径、失败重试次数这些信息必须落盘,否则重启就回到原点。磁盘 IO 和网络 IO 的边界要划清楚,别让一次写失败导致内存缓存和磁盘状态不一致。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











