http.FileServer 仅支持静态文件读取,不支持文件上传;需手动解析 multipart/form-data、校验字段、流式写入并维护状态,可用 req.MultipartReader() 安全处理大文件。

Go 文件上传为什么不能直接用 http.FileServer
因为 http.FileServer 只提供静态文件读取服务,不处理 POST 表单上传、分块接收或断点续传逻辑。真实场景中,你需要自己解析 multipart/form-data,校验字段,流式写入磁盘或对象存储,并维护上传状态。
用 req.MultipartReader() 安全接收大文件
直接调用 r.ParseMultipartForm(32 会把整个文件载入内存,容易被恶意上传打爆内存。正确做法是绕过自动解析,用 <code>req.MultipartReader() + io.Copy 流式处理:
mr, err := r.MultipartReader()
if err != nil { return }
for {
part, err := mr.NextPart()
if err == io.EOF { break }
if part.FormName() == "file" {
f, _ := os.OpenFile("upload.tmp", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
io.Copy(f, part) // 不缓冲,不加载全文
f.Close()
}
}
- 必须检查
part.FileName()防止空文件或路径遍历(如../../etc/passwd) - 不要用
part.Header.Get("Content-Length")做校验——它可能为空或被伪造 - 若需限速,用
io.LimitReader(part, maxBytes)包一层
断点续传依赖客户端传 Upload-Offset 和 Upload-Length
HTTP 协议本身不定义断点续传,主流方案是采用 tus v1.0 协议(已被 Dropbox、Cloudflare 等采用)。Go 中推荐用 tusd 库而非手写逻辑,因为要处理:OPTIONS 预检、PATCH 偏移写入、HEAD 查询进度、并发冲突、临时文件清理。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 服务端只需配置
tusd.Config.Store(如filestore.New("/tmp/tusd")),无需实现核心逻辑 - 客户端必须在每次
PATCH请求头带上Upload-Offset: 12345和Upload-Length: 10485760 - 注意:tus 默认要求上传 ID 是 UUID 格式;若用自定义 ID(如用户 ID + 时间戳),需重写
UnroutedHandler中的 ID 解析逻辑
本地存储断点续传的坑:文件锁和偏移校验不能少
如果不用 tus,而是在自己的 HTTP handler 中实现简易断点(比如用 Range 头),关键在于原子性和一致性:
立即学习“go语言免费学习笔记(深入)”;
- 用
os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)打开文件,再用f.Seek(offset, io.SeekStart)定位,避免覆盖已有内容 - 写入前必须校验当前文件长度是否等于客户端声称的
Upload-Offset,否则说明中间有其他写入者或客户端发错请求 - Linux 下建议加
flock(用github.com/gofrs/flock),Windows 下用LockFileEx,否则并发 PATCH 请求会导致文件损坏 - 不要用
os.Truncate清理未完成上传——它会破坏正在写入的文件,应改用带时间戳的临时目录 + 定期扫描清理
真正难的不是“怎么写”,而是“怎么确保多实例、网络中断、客户端崩溃后状态依然可恢复”。tus 的设计就是为了解决这些边界问题,自己造轮子时最容易在异常恢复逻辑上漏掉 case。

















