Go语言 创建+服务(通过HTTP)一个.ZIP文件,而不写入磁盘?

vwkv1x7d  于 2022-12-07  发布在  Go
关注(0)|答案(3)|浏览(107)

我想提供一个.ZIP文件,该文件是在运行中创建的,而不必将其写入磁盘(I/O将降低性能),并通过HTTP将其提供给客户端。
下面是我第一次尝试的方法:

func ZipServe(W http.ResponseWriter, R *http.Request) {

buf := new(bytes.Buffer)
writer := zip.NewWriter(buf)

// for the sake of this demonstration, this is the data I will zip
data := ioutil.ReadFile("randomfile.jpg")

f, err := writer.Create("randomfile.jpg")
if err != nil { 
    fmt.Println(err)
}

_, err = f.Write(data)
if err != nil {
    fmt.Println(err)
}

io.Copy(W, buf)

err := writer.Close()
if err != nil {
    fmt.Println(err)
}

}

这是不好的,因为.ZIP在下载后会损坏。我想这个问题与io有关。我是不是该换一种方法?

hmmo2u0o

hmmo2u0o1#

我发现有趣的是,只是为了测试想出了这个:
http://play.golang.org/p/JKAde2jbR3

package main

import (
    "archive/zip"
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func zipHandler(w http.ResponseWriter, r *http.Request) {
    filename := "randomfile.jpg"
    buf := new(bytes.Buffer)
    writer := zip.NewWriter(buf)
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        log.Fatal(err)
    }
    f, err := writer.Create(filename)
    if err != nil {
        log.Fatal(err)
    }
    _, err = f.Write([]byte(data))
    if err != nil {
        log.Fatal(err)
    }
    err = writer.Close()
    if err != nil {
        log.Fatal(err)
    }
    w.Header().Set("Content-Type", "application/zip")
    w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename))
    //io.Copy(w, buf)
    w.Write(buf.Bytes())
}

func main() {
    http.HandleFunc("/zip", zipHandler)
    http.ListenAndServe(":8080", nil)
}

我只是添加了一些头文件,如Content-TypeContent-Disposition
另外,我没有使用io.Copy(w, buf),而是直接写w.Write(buf.Bytes()),想知道这样是否更好?可能更有经验的用户可以澄清这一点。

1qczuiv0

1qczuiv02#

下面是一个使用io.Copy的简单方法。对于较大的文件,它的性能可能不如使用缓冲区,但它对我很有效:

func handleZip(w http.ResponseWriter, r *http.Request) {
    f, err := os.Open("main.go")
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        if err := f.Close(); err != nil {
            log.Fatal(err)
        }
    }()

    // write straight to the http.ResponseWriter
    zw := zip.NewWriter(w)
    cf, err := zw.Create(f.Name())
    if err != nil {
        log.Fatal(err)
    }

    w.Header().Set("Content-Type", "application/zip")
    w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", f.Name()))

    // copy the file contents to the zip Writer
    _, err = io.Copy(cf, f)
    if err != nil {
        log.Fatal(err)
    }

    // close the zip Writer to flush the contents to the ResponseWriter
    err = zw.Close()
    if err != nil {
        log.Fatal(err)
    }
}
w6mmgewl

w6mmgewl3#

评分最高的答案应该可以,但在大文件上会占用大量内存。它将整个文件读入内存。虽然它避免了写入磁盘,但在处理大文件时可能会有其他性能问题。
好消息是golang zip库能够在不将整个文件加载到内存的情况下进行流传输!下面是如何做到这一点的。
使用库的两个较低的代码选项(披露我是作者):
1.你可以使用一个专门的微服务,而不需要自己写它(披露我是作者):ZipStreamer
1.如果你想把这个合并到你自己的服务中,你可以使用同一个项目作为一个库。用ZipStream.NewStream创建你的流,并把它传递给构造函数http.ResponseWriter。请看server.go/streamEntries的例子,它设置了所有必要的HTTP头。它负责所有的事情,以保持内存和CPU低。
如果你想从头开始写,这里有一个例子,关键是使用像io.Copy这样的东西,它有一个32 kb的内置缓冲区和一个缓冲的读取器;这样您就不会将整个文件加载到内存中,并且它应该可以扩展到任何大小的文件。

func zipHandler(w http.ResponseWriter, r *http.Request) error {
    // You need to write the headers and status code before any bytes
    w.Header().Set("Content-Type", "application/zip")
    // the filename which will be suggested in the save file dialog
    w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", "attachment.zip"))
    w.WriteHeader(http.StatusOK)

    zipWriter := zip.NewWriter(w)

    for _, entry := range <<yourListOfFiles>> {
        header := &zip.FileHeader{
            Name:     entry.pathInNewZip,
            Method:   zip.Store, // deflate also works, but at a cost
            Modified: time.Now(),
        }
        entryWriter, err := zipWriter.CreateHeader(header)
        if err != nil {
            return err
        }

        fileReader := bufio.NewReader(entry.pathToFile)

        _, err = io.Copy(entryWriter, fileReader)
        if err != nil {
            return err
        }

        zipWriter.Flush()
        flushingWriter, ok := z.destination.(http.Flusher)
        if ok {
            flushingWriter.Flush()
        }
    }

    return zipWriter.Close()
}

相关问题