Search code examples
pythonhttptcpserver

How to let user download file while it's being generated


I want to let the user start downloading a file when it's not ready yet. I don't want to send user to some page saying "Wait 30 seconds, file is being prepared." I can't generate the file in advance. I need user to click/send form, choose download location and start downloading. Generated file will be zip, so I imagine, that it should be possible to send file name with first few bytes of zips (which are always same) and before the file is generated don't confirm that tcp packet was send correctly or something like that, and after the file is generated send the rest.

How do I do that? Is there any tool which can do that. Or is there some better way? More high level solution better, C isn't my strong suit. Preferably in Python. Thanks.

File being generated is zip and before it's prepared there isn't really anything to send yet. Basically according to input I generate set of files (which takes few dozens seconds), than I zip them and serve to user. My application is in python on linux but which server I'll use isn't really important.


Solution

  • Despite claims it's impossible, I managed to find a way. I learned a bit of Go in a meantime, so I used that, but I guess it won't be too different in other languages.

    Basically first byte is written in the writer and then flushed. Browser than waits for rest.

    package main
    
    import (
        "bytes"
        "fmt"
        "io/ioutil"
        "net/http"
        "os"
        "strings"
        "time"
    )
    
    func Zip(w http.ResponseWriter, r *http.Request) {
        file_name := r.URL.Path
        file_name = strings.TrimPrefix(file_name, "/files/")
    
        w.Header().Set("Content-type", "application/zip")
        w.Write([]byte{80})
        if f, ok := w.(http.Flusher); ok {
            f.Flush()
        }
        for {
            if _, err := os.Stat("./files/" + file_name); err == nil {
                fmt.Println("file found, breaking")
                break
            }
            time.Sleep(time.Second)
        }
        stream_file_bytes, err := ioutil.ReadFile("./files/" + file_name)
        if err != nil {
            fmt.Println(err)
            return
        }
    
        b := bytes.NewBuffer(stream_file_bytes)
        b.Next(1)
        b.WriteTo(w)
        fmt.Println("end")
    }
    
    func main() {
        http.HandleFunc("/files/", Zip)
        if err := http.ListenAndServe(":8090", nil); err != nil {
            panic(err)
        }
    }