http.FileServer 不适用于大文件上传下载,因其无流式控制、内存占用高、不支持断点续传与并发限速;需用 multipart.Reader 流式解析并 os.File 追加写入,配合 io.CopyN 或带缓冲的 io.Copy 控制内存与吞吐。

为什么 http.FileServer 直接用在大文件上传下载上会出问题
它根本没做流式控制,上传时所有数据先读进内存再写磁盘,1GB 文件可能吃掉 2GB 内存;下载时默认不设 Content-Length 或分块传输,客户端容易超时或断连。更麻烦的是,它不支持断点续传、进度回调、并发限速这些生产必需能力。
- 上传场景下,
request.ParseMultipartForm默认限制 32MB,超限直接返回http: request body too large - 下载时若用
io.Copy直接转发文件句柄但没设ResponseWriter的缓冲区,小包频繁 syscall 会拖慢吞吐 - HTTP/1.1 下没有
Range处理逻辑,无法支持视频拖拽、断点续传
用 multipart.Reader + os.File 流式解析上传文件
绕过 ParseMultipartForm 的内存陷阱,手动读取 multipart boundary,边读边写磁盘,内存占用稳定在几 KB。
- 调用
r.MultipartReader()获取*multipart.Reader,再用NextPart()遍历每个 part - 对文件 part,用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)打开追加写入,避免覆盖 - 务必用
io.CopyN(dst, src, part.Size)或带 buffer 的io.Copy(如bufio.NewWriterSize(f, 1),别裸调io.Copy - 上传完成前不要 close 文件句柄,否则部分数据可能丢失(尤其在高并发下)
// 示例:流式保存上传文件
part, err := mr.NextPart()
if err == nil && part.Header.Get("Content-Disposition") != "" {
f, _ := os.OpenFile("upload.bin", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
defer f.Close()
buf := make([]byte, 1<<16)
for {
n, _ := part.Read(buf)
if n == 0 { break }
f.Write(buf[:n])
}
}
下载时必须支持 Range 请求与 Content-Range 响应
否则前端播放器、下载工具无法拖动或重试,用户感知就是“卡死”或“只能从头下”。Golang 标准库的 http.ServeContent 已内置完整 Range 处理,但必须传入正确参数。
- 文件必须用
os.Open打开(不能用os.ReadFile),且stat.Size()要准确 - 调用
http.ServeContent(w, r, filename, modTime, file),其中file是*os.File,不是[]byte - 如果文件是动态生成(如 zip 打包),无法用
ServeContent,得手动解析r.Header.Get("Range"),用file.Seek(offset, 0)定位再io.CopyN - 注意 Nginx 等反向代理可能吞掉
Range头,需配置proxy_set_header Range $http_range;
上传进度与并发控制靠中间件 + 临时文件名隔离
Go 本身不提供上传进度钩子,得在流式写入时嵌入计数逻辑,并把进度存在内存或 Redis 中——但要注意并发安全和清理时机。
专业的企业网站管理系统,专为中小企业公司开发设计,能让企业轻松管理网站,强大的后台功能,可随意增减栏目,有多种企业常用的栏目模块功能。多级分类,管理文章,图片,文字编辑,留言管理,人才,软件下载等。可让企业会上网就会管理网站,轻松学会使用。 系统功能模块有:单页(如企业简介,联系内容等单页图文)、文章(新闻)列表、产品(图片、订单、规格说明等)、图片、下载、人才招聘、视频、机构组识、全国销售网点图
立即学习“go语言免费学习笔记(深入)”;
- 为每个上传请求生成唯一 ID(如
uuid.NewString()),用作临时文件名和进度 key - 写文件时每写入 1MB 更新一次 Redis 中的
upload:progress:{id},值为已写入字节数 - 用
sync.WaitGroup或context.WithTimeout控制单个上传最大耗时,防止慢连接占满 goroutine - 上传失败或超时后,必须触发清理函数删临时文件,否则磁盘会被撑爆(建议用
os.RemoveAll+ 定期扫描过期文件)
真正难的不是代码怎么写,而是临时文件生命周期管理、Redis 进度一致性、以及反向代理对 Connection: keep-alive 和 Transfer-Encoding: chunked 的兼容性——这些地方一漏,线上就丢文件或卡死。









