核心问题是web浏览器没有在页面导航被取消时触发事件,但有一个在页面完成加载时触发的事件。任何外部的直接浏览器事件都将是一个优点和缺点的黑客。
有四种已知的方法来处理检测浏览器下载何时开始:
调用fetch(),检索整个响应,附加带有下载属性的a标记,并触发单击事件。现代网络浏览器将为用户提供保存已经检索到的文件的选项。这种方法有几个缺点:
整个数据团存储在RAM中,因此如果文件很大,它将消耗同样多的RAM。对于小文件,这可能不是问题。
用户必须等待整个文件下载完成后才能保存。他们也不能离开页面,直到它完成。
未使用内置的web浏览器文件下载器。
除非设置了CORS报头,否则跨域获取可能会失败。
使用iframe +服务器端cookie。如果页面在iframe中加载而不是开始下载,iframe会触发一个加载事件,但如果下载开始,它不会触发任何事件。在web服务器上设置cookie可以被JavaScript循环检测到。这种方法有几个缺点:
服务器和客户机必须协同工作。服务器必须设置cookie。客户端必须检测cookie。
跨域请求将无法设置cookie。
每个域可以设置多少个cookie是有限制的。
不能发送自定义HTTP报头。
使用带有URL重定向的iframe。iframe启动一个请求,一旦服务器准备好文件,它将转储一个HTML文档,该文档执行元刷新到一个新的URL,这将在1秒后触发下载。iframe上的load事件发生在HTML文档加载时。这种方法有几个缺点:
服务器必须维护所下载内容的存储。需要cron作业或类似作业来定期清理目录。
当文件准备好时,服务器必须转储特殊的HTML内容。
在从DOM中删除iframe之前,客户端必须猜测iframe何时实际向服务器发出了第二个请求,以及下载实际何时开始。这可以通过将iframe留在DOM中来解决。
不能发送自定义HTTP报头。
Use an iframe + XHR. The iframe triggers the download request. As soon as the request is made via the iframe, an identical request via XHR is made. If the load event on the iframe fires, an error has occurred, abort the XHR request, and remove the iframe. If a XHR progress event fires, then downloading has probably started in the iframe, abort the XHR request, wait a few seconds, and then remove the iframe. This allows for larger files to be downloaded without relying on a server-side cookie. There are several downsides with this approach:
There are two separate requests made for the same information. The server can distinguish the XHR from the iframe by checking the incoming headers.
A cross-domain XHR request will probably fail unless CORS headers are set. However, the browser won't know if CORS is allowed or not until the server sends back the HTTP headers. If the server waits to send headers until the file data is ready, the XHR can roughly detect when the iframe has started to download even without CORS.
The client has to guess as to when the download has actually started to remove the iframe from the DOM. This could be overcome by just leaving the iframe in the DOM.
Can't send custom headers on the iframe.
如果没有适当的内置web浏览器事件,这里就没有任何完美的解决方案。然而,根据您的用例,上述四种方法中的一种可能比其他方法更适合。
只要可能,动态地将响应流发送到客户端,而不是先在服务器上生成所有内容,然后再发送响应。各种文件格式可以流,如CSV, JSON, XML, ZIP等。这真的取决于找到一个支持流媒体内容的库。当请求一开始就流化响应时,检测下载的开始并不重要,因为它几乎马上就开始了。
另一种选择是预先输出下载标题,而不是等待所有内容先生成。然后生成内容,最后开始发送到客户端。用户内置的下载程序将耐心等待数据到达。缺点是底层网络连接可能会超时等待数据开始流动(无论是在客户端还是服务器端)。