Go实现断点续传关键在于客户端精准Seek写入、服务端真实返回206及合法Content-Range、彻底禁用os.O_APPEND;须通过HEAD/GET试探请求验证支持,用os.OpenFile(O_WRONLY|O_CREATE)配合Seek或WriteAt写入,并持久化.state文件校验断点一致性。

Go 实现文件断点续传,关键不在“加不加 Range 头”,而在于客户端能否精准定位写入位置、服务端是否真正响应 206 并返回合法 Content-Range、以及本地文件操作是否绕开了 os.O_APPEND 这个陷阱。
怎么发 Range 请求并判断服务端真支持
光看响应头有 Accept-Ranges: bytes 不行——Nginx、CDN 常伪造它,但实际对 Range 返回 200 或 500。必须发试探请求验证:
- 用
HEAD或GET带Range: bytes=0-1023请求目标 URL - 只当响应码是
http.StatusPartialContent(206)且Content-Rangeheader 匹配(如bytes 0-1023/12345678)才启用续传 - 若返回 200,说明服务端忽略
Range,应清空本地文件走全量下载 - 若返回 416,说明范围越界,需用
HEAD拿到真实Content-Length再重试
为什么不能用 os.O_APPEND 写文件
os.O_APPEND 是断点续传最隐蔽的坑:它会让所有 Write() 忽略之前 Seek() 的结果,强制写到文件末尾。结果就是数据错位、中间填充空白、甚至覆盖已有内容。
- 正确打开方式:
os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)(不带O_APPEND) - 写入前必须显式调用
file.Seek(offset, io.SeekStart),并检查返回值是否等于offset - 更安全的写法是
file.WriteAt(data, offset),它不依赖当前文件偏移量 - 别用
bufio.Writer包裹,缓冲会破坏Seek和实际写入位置的一致性
如何安全持久化和校验断点位置
断点信息不能只存在内存里,也不能只靠文件大小判断——文件可能被手动截断或篡改。
立即学习“go语言免费学习笔记(深入)”;
- 把已下载字节数写进同名
.download.state文件(如foo.zip.download.state),内容就一行纯数字 - 每次启动先读该文件,再用
os.Stat校验原文件大小是否匹配;不匹配就清空状态重下 - 写状态文件必须用
os.WriteFile(原子替换),别用os.Create + Write,否则中断时可能留下半截状态 - 对大文件,建议每写完一个分块后校验该块哈希(如 SHA256),服务端可比对
X-Prev-Chunk-Hash请求头防止错位上传
服务端响应 Range 时最容易漏掉的三件事
客户端能发对,不代表服务端能回对。标准库 http.ServeFile 默认支持,但自定义 handler 很容易漏掉关键响应细节:
- 状态码必须是
http.StatusPartialContent(206),不是 200 -
Content-Rangeheader 格式必须严格为bytes <var>start</var>-<var>end</var>/<var>total</var>,比如bytes 1024-9999/10000 -
Content-Length应设为本次返回的字节数(end - start + 1),不是整个文件长度 - 别用
io.Copy直接吐整个文件——要用io.CopyN(w, r, n)或io.LimitReader(file, n)精准控制输出长度,否则可能越界
真正难的不是写几行 req.Header.Set("Range", ...),而是让客户端 Seek、服务端 Range 解析、文件系统行为、网络传输边界全部对齐。任何一环松动,断点就会变成“断点错位”或“断点覆盖”。


















