登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go 文件下载怎么支持断点续传和 Range 请求

来源:17golang原创

时间:2026-09-06 00:41:51 260浏览 收藏

文件下载接口能返回 200,并不代表支持断点续传。真正的判断是:客户端带上 Range: bytes=... 后,服务端是否能从指定偏移读取,并返回 206 Partial Content 与正确的 Content-Range。在 Go 里,最小可靠方案通常是打开文件后直接调用 http.ServeContent,不要手写一套 Range 解析器。

把资源包装成可正常 Seekio.ReadSeeker,再交给 http.ServeContent,标准库会负责范围读取、文件大小判断和常见条件请求;应用代码重点放在路径权限、文件版本和错误记录。
要点速览
  • Range 成功时看 206Content-Range,不是看下载器界面。
  • ServeContent 依赖 io.ReadSeeker,并会先 Seek 到末尾获取大小。
  • 断点文件发生变化时,用稳定的 ETag 或修改时间让客户端重新判断。

先确认断点续传真正依赖哪些 HTTP 响应

Range 的单位是字节。例如请求 bytes=1000-1999,成功响应应为 206 Partial Content,并带 Content-Range: bytes 1000-1999/总大小。没有 Range 时返回完整文件,通常是 200;范围超出资源大小且无法满足时,服务端可能返回 416。

请求情况重点结果排查含义
无 Range200 + 完整长度普通下载路径
有效 Range206 + Content-Range断点读取生效
无效或不重叠范围416检查文件大小与客户端保存的偏移

这几个响应比“浏览器是否显示继续下载”更适合写入接口日志。下载器可能因为代理、缓存或服务器配置改成重新下载,不能只凭 UI 下结论。

用 http.ServeContent 实现最小下载接口

下面的处理器只允许从固定目录取文件名,避免把用户输入直接拼成任意路径。os.File 已经实现 io.ReadSeeker,因此标准库可以定位到范围起点,并在需要时设置内容类型和长度。

package main

import (
	"net/http"
	"os"
	"path/filepath"
)

func downloadHandler(w http.ResponseWriter, r *http.Request) {
	// 只接受单层文件名,避免用户输入逃逸出下载目录。
	name := filepath.Base(r.URL.Query().Get("name"))
	if name == "." || name == "" {
		http.Error(w, "missing file name", http.StatusBadRequest)
		return
	}

	// 文件打开成功后交给 ServeContent;它会读取 Range 并计算文件大小。
	f, err := os.Open(filepath.Join("./downloads", name))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	defer f.Close() // 请求结束释放文件描述符。

	info, err := f.Stat()
	if err != nil || info.IsDir() {
		http.NotFound(w, r)
		return
	}
	http.ServeContent(w, r, info.Name(), info.ModTime(), f)
}

func main() {
	// 路由只暴露下载处理器,生产环境还应叠加鉴权和限速。
	http.HandleFunc("/download", downloadHandler)
	http.ListenAndServe(":8080", nil)
}
Go http.ServeContent 将 Range 请求连接到可 Seek 文件并返回 206 的静态结构图
图1:查看 Range 请求经过 http.ServeContent 后如何连接到可 Seek 文件和局部响应。

这里没有手动设置 Accept-RangesContent-LengthContent-Range。让同一套标准库逻辑处理这些字段,能减少“状态码对了但范围头错了”的组合问题。路径校验、鉴权、访问审计和限速仍属于应用层职责。

处理文件变化、缓存校验和 Seek 边界

断点续传隐含一个前提:客户端保存的字节偏移仍对应同一份文件。如果服务端在中途覆盖了文件,继续拼接会得到损坏内容。可以让文件名对应不可变对象,或在响应中设置稳定的 ETag;官方文档说明,调用者预先设置符合 RFC 7232 的 ETag 后,ServeContent 可以参与 If-MatchIf-None-MatchIf-Range 的处理。

modtime 不是万能版本号:它可帮助生成 Last-Modified,但高频覆盖、时钟精度和代理缓存都可能让它不够稳定。对可变文件,发布流程更适合“写临时文件、校验完成、原子改名”,让下载请求只看到完整版本。

Go 文件下载中文件版本、ETag、If-Range 与 Seek 能力的静态边界关系图
图2:区分文件版本、缓存校验和 Seek 能力,定位断点下载失效的结构性原因。

另一个常见坑是把数据库 BLOB、网络响应或只能顺序读取的对象直接塞给 ServeContent。它需要通过 Seek 到末尾确定大小,并回到开头;不能可靠 Seek 的数据源就不满足这个接口契约。此时应先落盘,或实现一个能根据偏移读取的专用处理层,而不是伪造一个错误的文件大小。

上线前检查下载请求的关键结果

用浏览器开发者工具、客户端日志或反向代理日志观察同一个文件的两次请求:第一次无 Range,第二次带上保存的偏移。检查清单如下:

  • 请求的 Range 是否是字节范围,起点是否小于当前文件大小。
  • 有效范围是否得到 206,且 Content-Range 的总大小与当前文件一致。
  • 文件更新后,ETag 或 Last-Modified 是否变化,客户端是否重新开始。
  • 高并发下载时,打开文件、关闭文件和鉴权失败是否都有可检索日志。

如果始终是 200,优先确认请求是否真的到达 Go 服务,以及中间层是否剥离了 Range;如果收到 416,先比较客户端偏移与服务端当前大小。只有确认请求头和响应头之后,才值得继续查下载器本身。

相关问题

ServeContent 和 ServeFile 该怎么选?

已有安全路径并直接服务磁盘文件时可以考虑 ServeFile;需要自定义名称、修改时间或其他可 Seek 数据源时,ServeContent 更直接。两者都不能替代路径权限控制。

Range 请求一定会返回 206 吗?

不一定。范围无效、资源变化或中间层改写都可能导致错误或完整响应,判断时要同时看状态码和 Content-Range。

为什么内存 bytes.Reader 可以使用?

bytes.Reader 实现了 io.ReadSeeker,所以能提供定位能力;但大文件放在内存中会增加进程压力,通常应优先使用文件或专门的分段存储。

断点下载是否等于多线程分片下载?

不是。断点续传是客户端从已保存偏移继续取数据;多线程分片会同时发出多个范围请求,服务端还要考虑并发、限速和合并成本。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>