如何在 Go 中实现文件下载的暂停与续传功能
本文详解如何在Go语言中为HTTP文件下载添加真正的暂停(取消)与续传(Range请求)能力,涵盖http.Request.Cancel的正确用法、断点续传的核心原理、Range头设置、本地文件偏移写入及关键注意事项。

本文详解如何在 Go 语言中为 HTTP 文件下载添加真正的暂停(取消)与续传(Range 请求)能力,涵盖 http.Request.Cancel 的正确用法、断点续传的核心原理、Range 头设置、本地文件偏移写入及关键注意事项。
本文详解如何在 Go 语言中为 HTTP 文件下载添加真正的暂停(取消)与续传(Range 请求)能力,涵盖 `http.Request.Cancel` 的正确用法、断点续传的核心原理、Range 头设置、本地文件偏移写入及关键注意事项。
在 Go 中,“暂停下载”并非字面意义的线程挂起,而是主动取消当前请求;而“续传”本质上是发起新的 HTTP GET 请求,并通过 Range 请求头告知服务器从指定字节位置继续传输。这要求服务端支持 Accept-Ranges: bytes(绝大多数静态文件服务器默认支持),且客户端需维护已下载的字节数与本地文件状态。
✅ 实现步骤概览
- 首次下载:发送普通 GET 请求,边读边写入文件(记录总写入字节数);
- 暂停(取消):调用 cancelFunc() 关闭 context.WithCancel 创建的上下文;
- 续传准备:检查本地文件大小,作为 Range: bytes=
- 的起始位置; - 续传请求:设置 Range 头 + 使用 os.O_APPEND 或 os.SEEK_SET 定位写入位置;
- 校验一致性:建议比对 Content-Range 响应头与预期范围,避免中间状态错乱。
? 核心代码示例
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func downloadWithResume(url, filepath string, resume bool) error {
var offset int64 = 0
if resume {
if fi, err := os.Stat(filepath); err == nil {
offset = fi.Size()
}
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return err
}
// 设置 Range 头(仅续传时)
if offset > 0 {
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
return fmt.Errorf("unexpected status: %s (%d)", resp.Status, resp.StatusCode)
}
// 确保以追加模式打开(或 seek 到 offset)
f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
if offset > 0 {
if _, err := f.Seek(offset, io.SeekStart); err != nil {
return fmt.Errorf("seek failed: %w", err)
}
}
// 复制数据
n, err := io.Copy(f, resp.Body)
if err != nil && err != io.EOF {
return fmt.Errorf("copy failed: %w", err)
}
fmt.Printf("Downloaded %d additional bytes\n", n)
return nil
}
// 使用示例
func main() {
url := "https://httpbin.org/bytes/1000000"
file := "download.bin"
// 首次下载(不 resume)
fmt.Println("Starting initial download...")
if err := downloadWithResume(url, file, false); err != nil {
panic(err)
}
// 模拟暂停后续传(例如用户点击“继续”)
fmt.Println("Resuming download...")
if err := downloadWithResume(url, file, true); err != nil {
panic(err)
}
}⚠️ 关键注意事项
- 服务端必须支持 Range:可通过 HEAD 请求检查响应头是否含 Accept-Ranges: bytes;
- 不要混用 O_APPEND 和 Seek:O_APPEND 会忽略 Seek,续传务必使用 O_WRONLY + Seek;
- 响应头验证:成功续传时,Content-Range 应类似 bytes 12345-999999/1000000,需校验起始值是否等于 offset;
- 并发安全:若支持多任务下载/暂停,请用 sync.Mutex 保护文件句柄与状态变量;
- 取消 ≠ 立即停止 I/O:context.Cancel 触发后,http.Transport 会尽快中断连接,但底层 TCP 可能仍有少量缓冲数据。
✅ 总结
Go 中的“暂停/续传”是基于 HTTP 协议语义的协作式机制,而非操作系统级挂起。掌握 context 取消、Range 头构造、文件偏移写入三者联动,即可构建健壮的断点续传下载器。建议进一步封装为结构体(如 Downloader),支持进度回调、错误重试和校验和验证,以满足生产环境需求。
Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。
Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。
Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。
PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。
Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等
Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。













