如何在不进行内存分配的情况下使用gzip压缩并上传文件?

huangapple go评论90阅读模式
英文:

How to gzip and post a file without memory allocation

问题

有人知道我如何打开一个文件,边压缩边发送到服务器吗?我有一个文件读取器,还有一个http.Post()函数,我可以将读取器作为第三个参数传递进去。但是gzip.NewWriter()需要一个写入器并返回一个写入器。

我知道可以创建一个中间缓冲区来连接所有这些,但是我不想分配太多内存,因为这些文件可能非常大。有没有办法创建这个管道并将其传递给HTTP的POST请求?

英文:

Does anyone know how I can open a file, gzip it on the go and send it to a server using a post request?

I have a file that is a reader. Also, I have http.Post() function where I can pass the reader as a 3rd parameter.
But gzip.NewWriter() expects a writer and returns a writer.

I know that I can create some intermediary buffer to connect this all but I don't want to allocate much memory because these files can be quite big.
Is there a way to make this pipe and pass it to the HTTP post request?

答案1

得分: 6

是的,io.Pipe

    reader, writer := io.Pipe()
	go func() {
		defer w.Close()

		gw := gzip.NewWriter(w)
		defer gw.Close()

		gw.Write([]byte("替换为您的数据"))
	}()

	http.Post("some.url", "application/zip", reader)
英文:

Yes, io.Pipe

    reader, writer := io.Pipe()
	go func() {
		defer w.Close()

		gw := gzip.NewWriter(w)
		defer gw.Close()

		gw.Write( []byte("replace with your data"))
	}()

	http.Post("some.url", "application/zip", reader)

答案2

得分: 6

其他答案概述了如何使用io.Pipe。这个答案提供了更详细的信息,特别是关于错误处理方面。

func gzFileReader(fname string) (io.ReadCloser, error) {
    f, err := os.Open(fname)
    if err != nil {
        return nil, err
    }

    // 使用io.Pipe和一个goroutine来创建读取器
    // 读取应用程序写入的数据。
    r, w := io.Pipe()
    go func() {
        // 总是关闭文件。
        defer f.Close()

        // 将文件通过gzip复制到管道写入器。
        gzw := gzip.NewWriter(w)
        _, err := io.Copy(gzw, f)

        // 使用CloseWithError将错误传播回
        // 主goroutine。
        if err != nil {
            w.CloseWithError(err)
            return
        }

        // 刷新gzip写入器。
        w.CloseWithError(gzw.Close())
    }()
    return r, nil
}

使用这个函数的方式如下

body, err := gzFileReader("example.txt")
if err != nil {
    log.Fatal(err)
}
defer body.Close()

req, err := http.NewRequest("POST", "http://example.com/", body)
if err != nil {
    log.Fatal(err)
}
req.Header.Set("Content-Encoding", "gzip")
resp, err := http.DefaultClient.Do(req)
英文:

Other answers outline how to use io.Pipe. This answer shows more detail, particularly with regards to error handling.

func gzFileReader(fname string) (io.ReadCloser, error) {
	f, err := os.Open(fname)
	if err != nil {
		return nil, err
	}

	// Use io.Pipe and a goroutine to create reader
	// on data written by the appliation.
	r, w := io.Pipe()
	go func() {
		// Always close the file.
		defer f.Close()

		// Copy file through gzip to pipe writer.
		gzw := gzip.NewWriter(w)
		_, err := io.Copy(gzw, f)

		// Use CloseWithError to propgate errors back to
		// the main goroutine.
		if err != nil {
			w.CloseWithError(err)
			return
		}

		// Flush the gzip writer.
		w.CloseWithError(gzw.Close())
	}()
	return r, nil
}

Use the function like this:

body, err := gzFileReader("example.txt")
if err != nil {
	log.Fatal(err)
}
defer body.Close()

req, err := http.NewRequest("POST", "http://example.com/", body)
if err != nil {
	log.Fatal(err)
}
req.Header.Set("Content-Encoding", "gzip")
resp, err := http.DefaultClient.Do(req)

答案3

得分: 4

net/http.Request.Body 是一个 io.ReadCloser,因此它是实现了 io.Readerio.Closer 接口的任何值。也就是说,你可以调用 Read 方法来读取它的内容,然后最终调用 Close 方法来关闭它。

compress/gzipNewWriter 方法接受一个 io.Writer,也就是任何你可以向其写入数据的对象。

因此,我们需要将“压缩流”写入器与 HTTP 请求的“读取器”连接起来。这就是 io.Pipe 的作用。

所以,基本上的步骤如下:

  1. 打开源文件。
  2. 创建一个 io.Pipe
  3. 创建一个连接到管道写入端的 gzip 写入器。
  4. 创建一个 HTTP 请求,其请求体是管道读取端。
  5. 调用 io.Copy 方法来将数据从打开的文件复制到 gzip 写入器。
  6. 刷新 gzip 写入器并关闭它。

在这段代码中,始终要检查错误。

请注意,这段代码中不可避免地会使用一些内存缓冲区:io.Copy 大约使用 32KB 的缓冲区,io.Pipe 也会使用一些。虽然有一些方法可以减少这些数字,但我认为这是过早的优化。

另请注意,由于无法预测最终的压缩流的大小,你的 HTTP 客户端将使用分块传输编码(chunked transfer encoding)。

英文:

A net/http.Request.Body is an io.ReadCloser — hence it's any value which implements io.Reader and io.Closer interfaces. That is, you can call Read on it and then eventually Close it.

The NewWriter of compress/gzip accepts an io.Writer — an object of any type which you can Write to.

So we need to connect the "gzipped stream" writer with the HTTP request's body "reader".
That's where io.Pipe takes the stage.

So, basically:

  1. Open the source file.
  2. Create an io.Pipe.
  3. Create a gzip writer connected to the writing end of the pipe.
  4. Create an HTTP request whose body is the reading end of the pipe.
  5. Call io.Copy to shovel the data between the opened file and the gzipped writer.
  6. Flush the gzipped writer and close it.

Always check for errors everywere in this code.

Note that some memory buffers will inevitably be used: io.Copy uses something like 32 KiB and io.Pipe will, too.
There are ways to reduce these numbers but I'd consder this premature optimization.

Also note that since you cannot predict the side of the resulting gzipped stream, your HTTP client will use chunked transfer encoding.

huangapple
  • 本文由 发表于 2022年8月30日 22:30:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/73544174.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定