V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
MarkMelon
V2EX  ›  问与答

求助: gin 大文件上传

  •  
  •   MarkMelon · 2021-01-19 18:40:30 +08:00 · 3050 次点击
    这是一个创建于 1396 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    https://github.com/codeskyblue/gohttpserver/issues/98
    loading
        10
    loading  
       2021-01-19 20:22:24 +08:00 via Android
    还没有分片上传然后合并的中间件吗?坐等一个最优解。
    zu1k
        11
    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
        12
    coosir  
       2021-01-19 21:29:07 +08:00
    https://github.com/fabu-dev/fabu.dev
    这个项目里面上传文件用到了分片与合,可参考
    可以追下 /api/application/service/app.go 中的 Upload 方法
    MarkMelon
        13
    MarkMelon  
    OP
       2021-01-20 10:34:21 +08:00
    跪谢大家的回复 @Jirajine 这个可能是最优解 也是我想要的, 现在就是找一些比较标准的解析方案
    MarkMelon
        14
    MarkMelon  
    OP
       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
        15
    MarkMelon  
    OP
       2021-01-25 15:44:48 +08:00
    @zu1k 这个 ubuntu18 htop 实测内存会不断增大, ubuntu16.04 内存不增大,swap 增大

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

    最好的方式 还是我给的这种
    zu1k
        16
    zu1k  
       2021-01-25 16:02:44 +08:00 via Android
    @MarkMelon 我直接用的 gin 给的文件上传例子,在 windows 下测试的,我上传 4G 的文件的整个过程中,任务管理器中程序占用没有超过 100M
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2221 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:40 · PVG 08:40 · LAX 16:40 · JFK 19:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.