浏览器在下载动态内容时打开“另存为”窗口的原因是什么?

huangapple go评论162阅读模式
英文:

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

问题

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

HTML:

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

Server:

package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {

	// 处理下载请求。
	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)
        
        // 在我的开发机上,以下for循环大约需要30秒才能运行完毕。
		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")
	})

	// 启动HTTP服务器。
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log(err)
	}
}

func log(v ...interface{}) {
	fmt.Println(v...)
}

这个示例代码可以成功下载从download处理程序中的所有内容。然而,我观察到以下行为,我无法解释:

我将浏览器配置为始终询问下载文件的保存位置。当运行上述示例代码时,Chrome只在10秒的休眠之后但在for循环完成之前以及处理程序函数返回之前打开“另存为”窗口。为什么Chrome在发送“first message”之后的10秒休眠之前没有弹出“另存为”窗口?导致“另存为”窗口只在for循环开始时打开的消息与在for循环中发送的消息有什么不同?

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

英文:

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:

&lt;a href=&quot;download&quot; download&gt;Download&lt;/a&gt;

Server:

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

func main() {

	// Handle download request.
	http.HandleFunc(&quot;/download&quot;, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set(&quot;Content-Disposition&quot;, &quot;attachment; filename=\&quot;log.txt\&quot;&quot;)
		w.Write([]byte(&quot;first message\n&quot;))

		time.Sleep(10 * time.Second)
        
        // The following for-loop takes about 30 seconds to run on my dev machine.
		for i := 0; i &lt; 1000000; i++ {
			timestamp := time.Now().Unix()
			log(timestamp)
			w.Write([]byte(fmt.Sprintf(&quot;%v\n&quot;, timestamp)))
			time.Sleep(time.Microsecond)
		}

		log(&quot;done&quot;)
	})

	// Start HTTP server.
	if err := http.ListenAndServe(&quot;:8080&quot;, 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.

答案1

得分: 1

Go的http.ResponseWriterTransport级别上有一个默认的4KB缓冲区:

type Transport struct {
    // ...

    // WriteBufferSize指定在写入传输时使用的写入缓冲区的大小。
    // 如果为零,则使用默认值(当前为4KB)。
    WriteBufferSize int

    // ...
}

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

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

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

答案2

得分: 0

要让Firefox在发送"first message"后立即打开"另存为"窗口,似乎只需要使用Ricardo Souza的答案。要让Chrome执行相同的操作,还需要将响应的Content-Type头设置为除默认值text/plain以外的任何值(感谢此SO答案)。示例代码如下:

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

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(&quot;Content-Type&quot;, &quot;application/octet-stream; charset=utf-8&quot;)

huangapple
  • 本文由 发表于 2022年4月27日 05:56:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/72020962.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定