断点续传需手动设置range头并校验文件长度,用head获取content-length;写入时根据场景选o_append或seek+write;重试仅针对超时等可恢复错误,且须白名单检查200/206状态码。

如何用 http.Range 请求文件片段
断点续传本质是让服务端返回文件某一段字节,而不是全部。Golang 的 http.Request 本身不自动加 Range 头,必须手动设置:req.Header.Set("Range", "bytes=1024-2047")。注意起始偏移从 0 开始,且 Range 值不能超出文件总长度——否则服务端可能返回 416 Range Not Satisfiable。实际写时建议先发 HEAD 请求拿到 Content-Length,再决定是否发 Range 请求。
os.OpenFile 必须用 os.O_APPEND | os.O_WRONLY
追加写入已有文件时,不能用 os.O_CREATE | os.O_TRUNC,否则会清空已下载内容。正确打开方式是:os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)。但要注意:如果文件已存在且需要从指定 offset 写入(比如跳过已下载部分),O_APPEND 会强制写到末尾,此时得用 file.Seek(offset, io.SeekStart) + file.Write(),而非依赖 O_APPEND。
重试逻辑里必须区分 net.Error 和 HTTP 状态码
网络超时、连接被拒属于可重试错误;而 404、403、416 这类 HTTP 错误重试也没用。实操中建议:
- 用
errors.Is(err, context.DeadlineExceeded)或netErr, ok := err.(net.Error); ok && netErr.Timeout()判断超时 - 对
resp.StatusCode做白名单检查:只允许200(全量)和206(分段)继续写入 - 重试前 sleep 指数退避,比如
time.Sleep(time.Second ,避免压垮服务端
并发下载多个分片时如何避免文件错位
多个 goroutine 同时写一个文件,即使各自 seek 到不同 offset,仍可能因底层 write 缓冲或系统调用竞争导致数据错乱。安全做法是:
- 每个分片下载完后,把数据暂存到内存或临时文件,再由单个 goroutine 统一按 offset 写入目标文件
- 或者用
file.WriteAt(data, offset)替代file.Seek()+file.Write(),它原子性更强(但要求文件已存在且足够大) - 务必在写入前用
os.Truncate预分配文件大小,否则WriteAt可能因文件太小而静默失败
最麻烦的不是代码怎么写,而是服务端是否真正支持 Range ——有些 CDN 或反向代理会忽略或错误处理该头,结果返回完整文件却声称 206,导致本地文件重复叠加。上线前一定用 curl -H "Range: bytes=0-1023" -I 实测响应头是否含 Content-Range。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











