http.servefile不支持range断点续传,需手动解析range头、返回206状态码、设置content-range响应头、用os.open+seek+io.copyn精确传输字节。

Range 请求头没生效,http.ServeFile 直接忽略怎么办
Go 标准库的 http.ServeFile 默认不支持断点续传,它会忽略客户端发来的 Range 头,直接返回 200 + 全量内容。这不是 bug,是设计使然——它只做最简文件服务。
真正要支持分段下载,得自己处理 Range 解析、状态码、响应头和字节切片。核心不是“怎么加 Range”,而是“怎么绕过 http.ServeFile 的自动逻辑”。
- 必须手动读取请求头:
r.Header.Get("Range"),不能依赖中间件或框架自动解析 - 响应状态码必须是
206 Partial Content,不是200 OK - 必须显式设置
Content-Range和Accept-Ranges: bytes响应头 - 文件需用
os.Open(非http.ServeFile)打开,才能随机 seek 到指定偏移
如何安全解析 Range 头并校验边界
Range 头格式是 bytes=0-1023 或 bytes=500- 或 bytes=-500,标准允许单个范围(多范围在下载器中极少用,且 Go net/http 不原生支持)。解析错一个字符就会返回 416。
别手写正则,用 http.ParseRange —— 它专为此设计,会自动处理各种合法变体,并返回 [][]int64(每个子 slice 是 [start,end]),同时对非法格式返回错误。
- 调用前先检查
len(r.Header["Range"]) > 0,空头不走断点逻辑 -
http.ParseRange返回的end是包含端(inclusive),而io.CopyN或io.Copy需要的是长度,记得length = end - start + 1 - 务必校验
start ,否则返回 <code>http.StatusRequestedRangeNotSatisfiable (416) - 如果
start >= fileSize,也得返回 416,不能静默返回空响应
用 io.Copy 还是 io.CopyN?为什么必须用 io.CopyN
用 io.Copy 直接从 *os.File 拷贝到 http.ResponseWriter,它会一直读到文件末尾,完全无视你只想传 1MB 的意图。结果就是:本该返回 206 的响应,实际发了全量数据 + 200 状态码,客户端直接崩溃。
io.CopyN 才是唯一可靠选择:它严格按指定字节数拷贝,且返回实际拷贝数,可用于判断是否提前 EOF(比如磁盘损坏导致读不满)。
- 传入的
dst是http.ResponseWriter,src是*os.File(已Seek(start, 0)) - 第三个参数必须是精确计算出的长度:
end - start + 1,不是文件大小 - 如果
io.CopyN返回的n ,说明底层读失败,应中断并记录 error,不要强行补零 - 避免用
bufio.Reader包裹文件——它会破坏 seek 精度,导致偏移错位
大文件下 Seek 性能和并发安全要注意什么
每次请求都要 f.Seek(start, 0),对 SSD 影响小,但对机械盘或 NFS 存储,频繁随机 seek 会明显拖慢吞吐。更隐蔽的问题是:同一个 *os.File 被多个 goroutine 并发 Seek+Read,可能相互覆盖 offset,导致返回错乱字节。
- 绝对不要复用全局
*os.File变量供所有请求共用 - 每个请求都应
os.Open新文件句柄,处理完立刻Close(用defer f.Close()) - 若文件极热(如 CDN 场景),可考虑用
mmap(通过golang.org/x/exp/mmap)替代 seek+read,但会增加内存占用且 Windows 支持弱 - 注意 ulimit -n,高并发时大量
Open可能触发 “too many open files”,需配fs.FileServer缓存或限流
Range 下载真正的复杂点不在解析头,而在字节精度、状态码语义、文件句柄生命周期这三者的咬合——漏掉任一环,客户端就收不到正确分段,或者服务器悄悄泄漏 fd。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











