go - 如何上传 gzip 压缩文件而不将所有内容读入内存

标签 go

我想知道是否有人可以指出如何进一步优化?我不喜欢这样的事实:我必须将整个文件读入内存并创建文件长度的 byte slice 。

代码如下:

func newfileUploadRequestWithGzip(uri string, paramName, path string) (*http.Request, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }

    fi, err := f.Stat()
    if err != nil {
        return nil, err
    }
    defer f.Close()

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)
    part, err := writer.CreateFormFile(paramName, fi.Name())
    if err != nil {
        return nil, err
    }

    filebuffer := make([]byte, fi.Size())
    var gzbuffer bytes.Buffer

    gw, err := gzip.NewWriterLevel(&gzbuffer, gzip.DefaultCompression)

    buffer := bufio.NewReader(f)

    if _, err = buffer.Read(filebuffer); err != nil {
        fmt.Printf("Error in reading file with error: %v\n", err)
    }

    n, err := gw.Write(filebuffer)
    gw.Close()
    fmt.Printf("%d:%d => %.2f%%\n", n, len(gzbuffer.Bytes()), float32(len(gzbuffer.Bytes()))/float32(n)*100.0)

    io.Copy(part, &gzbuffer)

    if writer.Close() != nil {
        return nil, err
    }

    request, requestErr := http.NewRequest("POST", uri, body)
    request.Header.Add("Content-Type", writer.FormDataContentType())
    return request, requestErr
}

最佳答案

我认为没有一种方法可以在不将文件读入内存的情况下发送文件;但是,无需立即将其所有内容读入内存即可完成。

如果您不想立即将所有内容读入内存,那就不要这样做 - 并摆脱bytes.Buffergzip.NewWritermultipart.Writer.CreateFormFile 都可以很好地与 io.Writer 接口(interface)配合使用,而不仅仅是 bytes.Buffer >。 http.NewRequest,另一方面,需要一个io.Reader。虽然 bytes.Buffer 似乎是实现这两者的最佳选择,但它并不是唯一的选择。 io 包提供了 io.Pipe() 来创建高效的内存管道,这正是我们所需要的。

例如,

func newfileUploadRequestWithGzip(uri string, paramName, path string) (*http.Request, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }

    fi, err := f.Stat()
    if err != nil {
        f.Close()
        return nil, err
    }

    buf := bufio.NewReader(f)
    r, w := io.Pipe()

    multi := multipart.NewWriter(w)
    part, err := multi.CreateFormFile(paramName, fi.Name())
    if err != nil {
        f.Close()
        return nil, err
    }
    writer, err := gzip.NewWriterLevel(part, gzip.DefaultCompression)
    if err != nil {
        f.Close()
        return nil, err
    }

    go func() {
        _,err := io.Copy(writer, buf)
        w.Close()
        multi.Close()
        writer.Close()
        f.Close()
        if err != nil {
            panic(err) // panic is not good, but how to make it good depends.
        }
    }()

    request, requestErr := http.NewRequest("POST", uri, r)
    request.Header.Add("Content-Type", multi.FormDataContentType())
    return request, requestErr
}

这段代码非常臭,需要一些工作来重构。您的函数签名以及函数的范围(其职责)或您希望函数执行的操作需要更改,以实现更好的错误处理和资源管理。建议拆分函数 - 一个用于准备 io.Writers 和 io.Pipe() ,一个用于多部分处理,一个用于请求部分;将整个事情包装成一个类型并创建私有(private)帮助器方法,同时设置错误并使用 Err() error 方法来收集错误。但如果不了解用例,就很难做出决定并使其付诸实践——而且主要是基于意见。

@mh-cbon 提供了一个更好看的代码:https://play.golang.org/p/i5bocA6-Q4M

关于go - 如何上传 gzip 压缩文件而不将所有内容读入内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59191263/

相关文章:

go - 如何构建多语言存储库以与 golang 合理配合?

go - 如何关闭定时器 channel ?

go - 如何从不同的包中调用包

rest - 当某些字段为只读而其他字段可为空时,如何使用 Golang 结构在 API 中执行 CRUD?

dictionary - 如何安全地允许当前访问go中的嵌套 map ?

go - 如何将断言临时结构键入具体结构

go - 如何设置消费者从Golang Kafka 10中的特定偏移量开始

go - 根据类型断言等返回参数的数量更改行为

go - 将 Caddy 导入 go 项目依赖项失败

go - 下载模块的原始代理