http协议不携带分片元信息,客户端须通过url query或header显式传递upload_id、chunk_index等字段;分片应存临时目录并原子合并,同时配置超时与读取限制防内存耗尽。

为什么直接用 http.HandleFunc 处理分片会出错
因为 HTTP 协议本身不携带分片元信息(如当前块序号、总块数、文件唯一 ID),客户端若只发原始二进制数据,服务端无法判断这是新上传、续传还是乱序块。常见现象是:上传中途刷新页面后重试,服务端把新块写进旧临时文件,最终合并失败;或者多个用户同时上传同名文件,互相覆盖。
必须由客户端在请求中显式传递关键字段,推荐方案是通过 URL query 或 header 传:upload_id(UUID)、chunk_index、total_chunks、filename。避免放在 body JSON 里——那样得先读完整 body 才能解析,对大分片不友好,且和 multipart/form-data 混用时容易冲突。
-
upload_id必须全局唯一,建议客户端生成uuid.NewString(),服务端不做校验逻辑,只作目录隔离 - 不要依赖
Content-Rangeheader 做分片定位——Go 标准库不自动解析它,且部分前端 SDK(如 Uppy)默认不发 - 用 query 传参数比 header 更兼容:curl、Postman、浏览器 fetch 都支持,且不会被代理/CDN 无意过滤
如何安全地保存和合并分片文件
分片不能直接写入最终目标路径,否则并发写同一文件会导致数据损坏。正确做法是按 upload_id 建临时目录,每个分片存为 chunk_0001、chunk_0002 等独立文件,合并时按序 cat(Linux)或 ioutil.ReadAll(Go)拼接。
注意:不要用 os.Rename 替代复制——如果跨文件系统(比如 /tmp 在 tmpfs,最终目录在 SSD),Rename 会失败;也不要等所有分片到达再合并——客户端可能掉线,得支持超时清理。
- 临时目录路径建议:
./uploads/<code>upload_id/chunks/,权限设为0750防越权访问 - 每个分片接收后立刻
f.Sync(),防止断电丢数据;但别每块都f.Close()后再开——频繁 syscalls 影响吞吐 - 合并操作应原子化:先写
final.tmp,成功后再os.Rename("final.tmp", finalPath) - 加个后台 goroutine 定期扫描
./uploads/*下超过 24 小时未更新的目录并删除
net/http 中处理大分片的内存与超时陷阱
默认 http.Server 的 ReadTimeout 是 0(不限时),但实际上传大分片(如 100MB)时,TCP 层可能因网络抖动卡住几秒,导致连接被中间设备(Nginx、ALB)静默断开,而 Go 服务端还傻等。更危险的是,若用 io.Copy 直接写磁盘却不设限,恶意客户端可发超长 body 耗尽服务器内存。
- 务必设置
http.Server.ReadTimeout和WriteTimeout,例如5 * time.Minute,比单分片最大传输时间多留 2 分钟余量 - 用
http.MaxBytesReader包裹r.Body,限制单次请求 body 不超过 100MB:http.MaxBytesReader(w, r.Body, 100 - 禁用
http.ServeMux的自动 redirect(如访问/upload自动跳转/upload/),避免 POST 请求被 301 重定向后丢失 body - 不要用
bytes.Buffer缓存整个分片——改用os.CreateTemp写临时文件,边收边刷盘
客户端怎么发分片才让 Go 服务端不报错
最简可行的 curl 示例(调试用):
curl -X POST \ "http://localhost:8080/upload?upload_id=abc123&chunk_index=0&total_chunks=3&filename=test.zip" \ -H "Content-Type: application/octet-stream" \ --data-binary "@chunk_0.bin"
关键点:用 application/octet-stream,不用 multipart/form-data——后者需要解析 boundary,增加服务端复杂度;@chunk_0.bin 表示发送文件内容而非文件名字符串。
- 前端 JS 发送时,
fetch的body直接传File.slice()返回的 Blob,不要转成 base64 -
chunk_index从 0 开始,服务端用strconv.Atoi(r.URL.Query().Get("chunk_index"))解析,记得检查错误 - 上传完成请求(告知服务端可以合并了)应走另一个 endpoint,如
POST /upload/complete?upload_id=abc123,不要复用同一个路由 - 服务端收到分片后,返回
200 OK即可,不要返回大 JSON——移动端弱网下可能触发重试
分片上传真正难的不是接收,而是状态一致性:upload_id 生命周期管理、失败重试的幂等性、跨节点部署时的共享存储选型。这些在单机版 demo 里可以忽略,但上线前必须想清楚。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











