英文:
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.Reader
和 io.Closer
接口的任何值。也就是说,你可以调用 Read
方法来读取它的内容,然后最终调用 Close
方法来关闭它。
compress/gzip
的 NewWriter
方法接受一个 io.Writer
,也就是任何你可以向其写入数据的对象。
因此,我们需要将“压缩流”写入器与 HTTP 请求的“读取器”连接起来。这就是 io.Pipe
的作用。
所以,基本上的步骤如下:
- 打开源文件。
- 创建一个
io.Pipe
。 - 创建一个连接到管道写入端的 gzip 写入器。
- 创建一个 HTTP 请求,其请求体是管道读取端。
- 调用
io.Copy
方法来将数据从打开的文件复制到 gzip 写入器。 - 刷新 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:
- Open the source file.
- Create an
io.Pipe
. - Create a gzip writer connected to the writing end of the pipe.
- Create an HTTP request whose body is the reading end of the pipe.
- Call
io.Copy
to shovel the data between the opened file and the gzipped writer. - 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论