求助: gin 大文件上传

2021-01-19 18:40:30 +08:00
 MarkMelon

目的: 让 http body 中全是文件内容, 不包含其他东西。

因为大文件直接上传到服务器上会占用大量内存, 为了节省内存采用了流式读取 http body, 然后写入文件
但是 body 中可能有其他 post 参数, 如何边读边解析出文件内容和这些参数呢?

已知可以让前端 body 采用 binary 格式, 但是受限于前端组件不支持这种格式, 推进很困难。

3067 次点击
所在节点    问与答
16 条回复
shoaly
2021-01-19 19:00:56 +08:00
拆分, 把单纯的文件上传剥离出来, 上传好来 返回一个 media_id , 然后将 media_id 和 其他 post 参数 再第二次传过来
MarkMelon
2021-01-19 19:08:05 +08:00
@shoaly 首先感谢。 我们现在已经让前端把大文件拆成小文件上传了。
现在要解决的是, 流式读取 body 并保存到文件中。 比如每次从网卡里读 4k 这样的。
但是 body 里 form/data 会有分隔符还有其他参数。 如何在流式读取过程中把文件和这些参数区分开
shoaly
2021-01-19 19:10:54 +08:00
@MarkMelon 是说把文件上传 和 其他参数上传拆开, 文件先上传 变成 参数之一
Jirajine
2021-01-19 19:12:17 +08:00
把文件和其他参数混到一个 body 中不是合适的做法,但即使这么作了也不影响流式解析。以 JSON 为例,先搞个 json.Decoder 把那些参数读出来,然后剩下的在写到磁盘。
MarkMelon
2021-01-19 19:37:24 +08:00
@Jirajine 主要是如何在读流的过程中解析参数
MarkMelon
2021-01-19 19:37:59 +08:00
@shoaly 这种可能违背了需求本身了
jindeq
2021-01-19 19:58:20 +08:00
form-data 形式的表单是支持的,我现在也有个接口在用。gin 可以取 body 里的参数和文件
Jirajine
2021-01-19 20:07:51 +08:00
@MarkMelon 要是文件数据包含到参数里面那就稍微麻烦点,你得手动解析。
具体就是流式地读,读到参数部分就解析出来,读到文件部分就写到磁盘上,再读到参数部分再解析出来,以此类推。
matrix67
2021-01-19 20:14:10 +08:00
上传大文件还挺麻烦的,这个 1.1k ,go 下面 star 最多的 httpserver,实现的就有问题,大文件上传会挂掉。

https://github.com/codeskyblue/gohttpserver/issues/98
loading
2021-01-19 20:22:24 +08:00
还没有分片上传然后合并的中间件吗?坐等一个最优解。
zu1k
2021-01-19 21:20:43 +08:00
其实完全不需要担心,net/http 在 readForm 的时候如果文件超过指定的最大内存占用,会自动写入临时文件,所以根本不会占用太多内存

https://github.com/golang/go/blob/master/src/mime/multipart/formdata.go#L91
coosir
2021-01-19 21:29:07 +08:00
https://github.com/fabu-dev/fabu.dev
这个项目里面上传文件用到了分片与合,可参考
可以追下 /api/application/service/app.go 中的 Upload 方法
MarkMelon
2021-01-20 10:34:21 +08:00
跪谢大家的回复 @Jirajine 这个可能是最优解 也是我想要的, 现在就是找一些比较标准的解析方案
MarkMelon
2021-01-25 15:43:15 +08:00
partReader, err := ctx.Request.MultipartReader()
if err != nil {
this.logger.WithError(err).Error("UploadChunk invalid http body.")
result.ErrorCode = this.getErrorCodes().ParamError
return
}
for {
part, err := partReader.NextPart()
if part == nil && err == io.EOF {
break
}
if err != nil && err != io.EOF {
this.logger.Error("read body failed, err: ", err)
if err = os.Remove(chunkPath); err != nil {
this.logger.Error("read body failed remove chunk file failed, err: ", err)
}
result.ErrorCode = this.getErrorCodes().ChunkReadError
return
}
if part.FileName() != "" {
// 从 http 流中循环读取并写到文件中
_, err := io.CopyBuffer(file, part, buf)
// _, err := io.CopyBuffer(file, ctx.Request.Body, buf) // 直接解析 http body, 读取 binary 数据,body 中只包含文件内容
if err != nil {
this.logger.Error("write data from part to file failed, err: ", err)
if err = os.Remove(chunkPath); err != nil {
this.logger.Error("write data from part to file failed and remove chunk file failed, err: ", err)
}
result.ErrorCode = this.getErrorCodes().ChunkWriteError
return
}
}
}
MarkMelon
2021-01-25 15:44:48 +08:00
@zu1k 这个 ubuntu18 htop 实测内存会不断增大, ubuntu16.04 内存不增大,swap 增大

后来发现 ubuntu18 和 ubuntu16 htop 统计方式变了。。。。

最好的方式 还是我给的这种
zu1k
2021-01-25 16:02:44 +08:00
@MarkMelon 我直接用的 gin 给的文件上传例子,在 windows 下测试的,我上传 4G 的文件的整个过程中,任务管理器中程序占用没有超过 100M

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/746403

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX