I am experimenting with an idea to stream dynamic data from a web server into a file on the client device. To implement this idea, I am making use of the HTTP Content-Disposition response header and the HTML download attribute. The following is my sample code, where the server is implemented in Go:
HTML:
<a href="download" download>Download</a>
Server:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// Handle download request.
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", "attachment; filename=\"log.txt\"")
w.Write([]byte("first message\n"))
time.Sleep(10 * time.Second)
// The following for-loop takes about 30 seconds to run on my dev machine.
for i := 0; i < 1000000; i {
timestamp := time.Now().Unix()
log(timestamp)
w.Write([]byte(fmt.Sprintf("%v\n", timestamp)))
time.Sleep(time.Microsecond)
}
log("done")
})
// Start HTTP server.
if err := http.ListenAndServe(":8080", nil); err != nil {
log(err)
}
}
func log(v ...interface{}) {
fmt.Println(v...)
}
This sample code works in that it successfully downloads all the content from the download
handler when clicking on the "Download" link. However, I am observing the following behavior that I am unable to explain:
I have my browser configured to always ask where to save the downloaded files. When running the sample code above, Chrome opens the Save As window only after the 10 second sleep but before the for-loop is complete and in turn the handler function has returned. Why did Chrome not present the Save As window when the "first message" was sent before the 10 second sleep? What is different between the "first message" and the messages being sent in the for-loop that causes the Save As window to only open when the for-loop starts?
Aside: If FileSystemWritableFileStream had greater cross-browser support, I'd use that to stream dynamic server data directly into a file on the client side.
CodePudding user response:
Go's http.ResponseWriter
has a default 4KB buffer, defined at the Transport level:
type Transport struct {
// ...
// WriteBufferSize specifies the size of the write buffer used
// when writing to the transport.
// If zero, a default (currently 4KB) is used.
WriteBufferSize int
// ...
}
In some instances, when using standard responses, you can make use the Flush
method by using type assertion with the http.Flusher
interface to send the bytes right away:
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
CodePudding user response:
To have Firefox open the Save As window immediately after "first message" is sent, Ricardo Souza's answer appears to be all that's needed. To have Chrome do the same, the response's Content-Type
header also needs to be set to anything other than the default text/plain
(thanks to this SO answer). Example:
w.Header().Set("Content-Type", "application/octet-stream; charset=utf-8")