Search code examples
gozippipestreamreaderwriter

Download a zip file using io.Pipe() read/write golang


I am trying to stream out bytes of a zip file using io.Pipe() function in golang. I am using pipe reader to read the bytes of each file in the zip and then stream those out and use the pipe writer to write the bytes in the response object.

func main() {
  r, w := io.Pipe()
 // go routine to make the write/read non-blocking
  go func() {

    defer w.Close()

    bytes, err := ReadBytesforEachFileFromTheZip() 
    err := json.NewEncoder(w).Encode(bytes)
    handleErr(err)
  }()

This is not a working implementation but a structure of what I am trying to achieve. I don't want to use ioutil.ReadAll since the file is going to be very large and Pipe() will help me avoid bringing all the data into memory. Can someone help with a working implementation using io.Pipe() ?


Solution

  • I made it work using golang io.Pipe().The Pipewriter writes byte to the pipe in chunks and the pipeReader reader from the other end. The reason for using a go-routine is to have a non-blocking write operation while simultaneous reads happen form the pipe.

    Note: It's important to close the pipe writer (w.Close()) to send EOF on the stream otherwise it will not close the stream.

    func DownloadZip() ([]byte, error) {
        r, w := io.Pipe()
    
        defer r.Close()
        defer w.Close()
    
        zip, err := os.Stat("temp.zip")
        if err != nil{
            return nil, err
        }
    
        go func(){
    
            f, err := os.Open(zip.Name())
            if err != nil {
                return
    
            }
    
            buf := make([]byte, 1024)
            for {
                chunk, err := f.Read(buf)
                if err != nil && err != io.EOF {
                    panic(err)
                }
                if chunk == 0 {
                    break
                }
    
                if _, err := w.Write(buf[:chunk]); err != nil{
                    return
                }
    
            }
    
            w.Close()
        }()
    
        body, err := ioutil.ReadAll(r)
        if err != nil {
            return nil, err
        }
        return body, nil
    
    }
    

    Please let me know if someone has another way of doing it.