是什么导致浏览器在下载动态内容时打开 "Save As" window?

What causes browsers to open "Save As" window when downloading dynamic content?

我正在尝试将动态数据从 Web 服务器流式传输到客户端设备上的文件中。为了实现这个想法,我正在使用 HTTP Content-Disposition response header and the HTML download attribute。以下是我的示例代码,其中服务端是用Go实现的:

HTML:

<a href="download" download>Download</a>

服务器:

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...)
}

此示例代码的工作原理是,当单击“下载”link 时,它会从 download 处理程序成功下载所有内容。但是,我观察到以下我无法解释的行为:

我将浏览器配置为 always ask where to save the downloaded files。当 运行 上面的示例代码时,Chrome 打开 另存为 window 仅在 10 秒睡眠之后但在 for 循环完成之前并且处理函数又返回了。为什么在 10 秒睡眠之前发送“第一条消息”时,Chrome 没有显示 另存为 window? “第一条消息”与在 for 循环中发送的导致 Save As window 仅在 for 循环开始时打开的消息有何不同?

旁白:如果 FileSystemWritableFileStream 有更好的跨浏览器支持,我会用它来将动态服务器数据直接流式传输到客户端的文件中。

Go 的 http.ResponseWriter 有一个默认的 4KB 缓冲区,定义在 Transport 级别:

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

    // ...
}

在某些情况下,当使用标准响应时,您可以通过使用带有 http.Flusher 接口的类型断言来使用 Flush 方法来立即发送字节:

if f, ok := w.(http.Flusher); ok { 
    f.Flush()
}

要让 Firefox 在“第一条消息”发送后立即打开 另存为 window,Ricardo Souza 的回答似乎已足够。要让 Chrome 执行相同的操作,还需要将响应的 Content-Type header 设置为默认值 text/plain 以外的任何值(感谢 this SO answer)。示例:

w.Header().Set("Content-Type", "application/octet-stream; charset=utf-8")