go中用http.request设置range头实现断点续传本质是让服务器返回文件部分字节,需手动设置"bytes=start-end"格式头,校验206响应与content-range,先head获取总长,超界时处理416错误,多片段须暂存再有序合并,避免直接writeat或o_append导致错位。

Go 中用 http.Request 设置 Range 头实现分片下载
断点续传本质是让服务器只返回文件的一部分,关键靠 HTTP Range 请求头。Go 的 http.Client 默认不加这个头,必须手动设置。
常见错误是直接拼字符串:"bytes=0-1023",但没校验边界、没处理服务器返回的 Content-Range,导致后续合并错位或覆盖。
- 起始偏移量从 0 开始,结束偏移量含在内(即
bytes=100-199表示第 100~199 字节,共 100 字节) - 若文件总大小未知,可先发一次 HEAD 请求,读取
Content-Length;若服务器不支持 HEAD 或返回 -1,则需靠首次 GET 的Content-Range推断 -
Range超出文件长度时,服务器通常返回 416(Requested Range Not Satisfiable),需捕获该错误并退出,而非忽略
并发下载多个 Range 片段并写入临时文件
单个 Range 下载完后不能直接追加到目标文件——因为片段可能乱序到达,必须写入独立临时文件或内存 buffer,再按 offset 合并。
更稳妥的做法是为每个片段分配一个 *os.File(带 O_CREATE|O_TRUNC),文件名含 offset,比如 part_1024_2047.tmp;下载完成后统一按 offset 排序拼接。
- 避免用
os.WriteAt直接写入目标文件:Windows 上不支持,Linux 上需文件已存在且足够大,否则会截断 - 并发数别设太高(如 >10),容易触发服务器限流或连接复用失败;建议用
semaphore控制 goroutine 数量 - 每个请求务必设置超时:
context.WithTimeout(ctx, 30*time.Second),否则卡住的请求会拖垮整个流程
合并临时片段前校验 Content-Range 和实际字节数
光看 HTTP 状态码 206 不够,必须比对响应头中的 Content-Range 和实际读取的字节数。常见坑是服务器返回了完整文件(状态码 200)却没报错,导致后续 offset 错乱。
例如响应头为 Content-Range: bytes 1024-2047/10000,但 body 只读了 900 字节,说明传输中断或被代理截断,该片段必须重试或放弃。
- 解析
Content-Range用正则不如用strings.Split+strings.TrimPrefix更快,例如:parts := strings.Split(header, "bytes ")[1]; rangeStr := strings.Split(parts, "/")[0] - 合并时用
io.Copy逐个复制片段文件,不要一次性os.ReadFile全部加载进内存——大文件(>1GB)会 OOM - 合并完成前,目标文件应保持为临时名(如
file.part),最后用os.Rename原子替换,避免部分写入暴露给其他进程
如何安全恢复中断的下载任务
恢复的关键不是“记住下了多少”,而是“哪些 Range 已完整写入且校验通过”。因此需要持久化记录已完成的 offset 区间,而不是简单查目标文件长度。
推荐做法:每次成功写完一个片段,就将 [start, end] 写入一个轻量元数据文件(如 JSON 数组),格式如 [{"start":0,"end":1023},{"start":1024,"end":2047}]。重启时读该文件,跳过已存在的区间。
- 元数据文件必须和临时片段放同一目录,并用
os.Sync()刷盘,否则崩溃后可能丢失最后一条记录 - 不要依赖目标文件的
Stat().Size():它可能被其他程序修改,或上次中断时写了一半但没更新元数据 - 如果元数据损坏或缺失,退化为全量重新下载——比错位合并更安全
真正麻烦的是服务器不支持 Range 或返回 200 而非 206,这种情况得 fallback 到普通流式下载,且无法断点续传。上线前务必用 curl -H "Range: bytes=0-1023" -I URL 实测服务端行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











