Buffalo 默认不支持大文件分片上传,因其基于 net/http 的 ParseMultipartForm 会将整个 multipart 请求体载入内存或磁盘,单分片超 MaxMemory(默认32MB)即报错或 panic;必须绕过自动解析,直接读取 ctx.Request().Body 并用 mime/multipart.Reader 手动处理 boundary。

Buffalo 框架本身不内置分片上传能力,必须自行实现逻辑,且需绕过默认的 multipart.FormValue 和 ctx.Request().MultipartReader() 限制。
为什么 Buffalo 默认不支持大文件分片上传
Buffalo 基于 Go 的 net/http,其 ParseMultipartForm 默认会将整个 multipart 请求体读入内存(或临时磁盘),一旦单个分片超过 MaxMemory(默认 32MB),就会触发 http.ErrMissingFile 或直接 panic;而分片上传要求每个请求只处理几 MB 的二进制流,不能走完整 form 解析流程。
- 所有分片请求必须用
ctx.Request().Body直接读取原始字节,跳过ctx.Request().MultipartForm - 不能依赖
ctx.Param("chunkIndex")传参——URL query 或 JSON body 更可靠 - Buffalo 的中间件(如
render、auth)若提前调用ParseMultipartForm,会破坏流式读取,需前置拦截
分片接收端必须手动解析 multipart boundary
前端用 FormData 提交分片时,仍会生成标准 multipart 请求。Buffalo 不提供低阶 boundary 解析工具,你得自己用 Go 标准库的 mime/multipart.Reader 处理:
Buffalo框架 1.0.1 版本源码包下载,适合需要错误处理改进、依赖更新、render.Download 注释和 request logger 调整的 v1 项目。
func handleChunk(c buffalo.Context) error {
r := c.Request()
// 禁止自动解析,否则 Body 被消耗
if err := r.ParseMultipartForm(1); err != nil && err != http.ErrNotMultipart {
return errors.WithStack(err)
}
// 手动重置 Body(仅当未被消耗时才安全)
if r.MultipartForm == nil {
r.Body = http.MaxBytesReader(c.Response(), r.Body, 10*1024*1024) // 限 10MB
}
mr, err := mime.NewReader(r.Body, getBoundary(r.Header.Get("Content-Type")))
if err != nil {
return errors.WithStack(err)
}
for {
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
return errors.WithStack(err)
}
switch p.FormName() {
case "file":
// 保存分片到磁盘,命名规则:{fileMd5}_{chunkIndex}.part
saveChunk(p, c.Param("fileMd5"), c.Param("chunkIndex"))
case "chunkIndex", "totalChunks", "fileMd5":
// 从 part 中读取字符串值,不要用 FormValue
}
}
return c.Render(200, render.JSON(map[string]string{"status": "ok"}))
}
-
getBoundary()需从Content-Typeheader 提取boundary=...字符串,不能硬编码 - 务必用
http.MaxBytesReader限制单次读取上限,防止恶意超大分片耗尽内存 - 避免调用任何触发
r.ParseMultipartForm的 Buffalo 方法(如c.Request().FormValue)
合并逻辑必须脱离 Buffalo 的表单生命周期
合并请求通常由前端在所有分片上传完成后发起,是纯 JSON POST,但 Buffalo 的 JSON 中间件默认会尝试解析整个 body —— 若你不控制大小,可能因 JSON 过长失败。更稳妥的做法是:
- 合并接口用
POST /api/merge,body 是轻量 JSON:{"fileMd5": "...", "fileName": "...", "totalChunks": 123} - 服务端用
io.LimitReader(c.Request().Body, 4096)读取并json.Unmarshal,避免无限制读取 - 合并动作应异步执行(例如发消息到
ants.Pool或简单 goroutine),防止 HTTP 请求超时;Buffalo 的 handler 必须快速返回 - 合并失败不能只返回 500,要写入日志并保留已上传分片,供下次重试
真正麻烦的不是代码量,而是 Buffalo 的“约定优于配置”哲学与分片上传的流式、状态化、异步本质天然冲突。你得主动放弃它对表单的封装,退回到 net/http 原始层操作 Body 和 Header —— 否则看似省事,实则踩坑更多。

















