需用http.NewRequest手动设Range头并处理206响应;通过Accept-Ranges或Content-Range判断服务端是否支持;文件写入须Seek定位而非O_APPEND;并发分片下载应独立文件句柄或用WriteAt加锁。

如何用 net/http 发起带 Range 头的 GET 请求
Go 标准库本身不自动支持断点续传,但完全可以通过手动设置 Range 请求头 + 处理 206 Partial Content 响应来实现。关键在于:你得自己维护已下载字节数,并在下次请求时填入正确的 Range: bytes=。start-
常见错误是直接复用 http.Get —— 它无法自定义请求头。必须用 http.NewRequest 构造请求,并显式设置 Range:
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
req.Header.Set("Range", "bytes=1024-") // 从第1024字节开始下载
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
注意:Range 值末尾不加结束位置(如 bytes=1024-)表示“从该位置到文件末尾”,服务端必须支持这种语法(绝大多数 HTTP 服务器都支持)。
如何判断服务端是否支持断点续传
不能只看状态码是否为 206;更可靠的方式是检查响应头中是否存在 Accept-Ranges: bytes 或 Content-Range 字段。如果服务端返回 200 OK 却没这些头,说明它根本不支持分块传输,强行续传会重复下载整个文件。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 首次请求先发一个带
Range: bytes=0-0的试探请求,看响应是否为206且含Content-Range - 若返回
200或416 Range Not Satisfiable,基本可判定不支持或文件太小(0-0超出范围) - 某些 CDN 或反向代理会屏蔽
Range头,此时即使源站支持,你也拿不到206
如何安全地追加写入已存在的文件
用 os.OpenFile 以 os.O_WRONLY | os.O_CREATE | os.O_APPEND 模式打开文件是错的 —— O_APPEND 会强制写到文件末尾,无法跳转到指定偏移量。断点续传必须用 os.O_WRONLY | os.O_CREATE 打开,再调用 file.Seek 定位。
示例关键步骤:
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
// 假设已下载 1024 字节,接下来要写入第 1024 字节起的位置
_, err = f.Seek(1024, 0)
if err != nil {
return err
}
io.Copy(f, resp.Body) // 直接写入,无需手动分块
注意:Seek 后必须检查错误;如果文件原本不存在,Seek 会失败,需先创建空文件或改用 os.Truncate 预分配大小(对大文件更友好)。
并发下载多个分片时要注意什么
可以切分 Range(如 0-1023、1024-2047)并发请求,但必须保证每个分片写入对应磁盘偏移,且避免多个 goroutine 同时写同一文件句柄 —— Go 的 *os.File 不是并发安全的。
推荐做法:
- 每个分片使用独立的
*os.File(通过os.OpenFile(..., os.O_WRONLY)重新打开),并用Seek定位后写入 - 或者统一用一个文件句柄,但用
sync.Mutex保护WriteAt(注意:不是Write),因为WriteAt是线程安全的 - 不要依赖
Content-Length判断总大小 —— 分片响应里这个值只是当前块长度;应首次用 HEAD 请求获取Content-Length头
最易被忽略的一点:HTTP/1.1 连接复用下,多个 Range 请求可能被服务端按顺序响应,但 Go 的 http.Transport 默认限制每 host 最多 2 个并发连接,需显式调大 MaxConnsPerHost 才能真正并发。










