检测浏览器何时接收文件下载

我有一个页面,允许用户下载动态生成的文件。生成需要很长时间,因此我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时收到文件,因此可以隐藏指示器。

我正在以隐藏的形式发出请求,该请求会发布到服务器,并以隐藏的iframe作为结果。这样一来,我就不会用结果替换整个浏览器窗口。我在iframe上侦听“加载”事件,希望下载完成后将触发该事件。

我随文件返回一个“ Content-Disposition:附件”标头,这将导致浏览器显示“保存”对话框。但是浏览器不会在iframe中触发“加载”事件。

我尝试的一种方法是使用多部分响应。因此它将发送一个空的HTML文件以及附加的可下载文件。例如:

Content-type: multipart/x-mixed-replace;boundary="abcde"

--abcde
Content-type: text/html

--abcde
Content-type: application/vnd.fdf
Content-Disposition: attachment; filename=foo.fdf

file-content
--abcde

这在Firefox中有效;它会收到一个空的HTML文件,触发“加载”事件,然后显示可下载文件的“保存”对话框。但是它在IE和Safari上失败;IE会触发“加载”事件,但不会下载文件,而Safari会下载文件(具有错误的名称和内容类型),并且不会触发“加载”事件。

一种不同的方法可能是调用开始文件创建,然后轮询服务器直到服务器就绪,然后下载已创建的文件。但是我宁愿避免在服务器上创建临时文件。

有谁有更好的主意吗?

阳光泡芙Tom2020/03/12 20:36:22

如果只想在显示下载对话框之前显示消息或加载程序gif,则快速解决方案是将消息放入隐藏的容器中,然后单击生成要下载文件的按钮,即可使该容器可见。然后使用jquery或javascript捕获按钮的focusout事件,以隐藏包含消息的容器

伽罗Tony小卤蛋2020/03/12 20:36:22

如果您下载的是已保存的文件,而不是保存在文档中,则无法确定下载的完成时间,因为它不在当前文档的范围内,而是在浏览器中的单独过程。

神乐西里2020/03/12 20:36:21

以我的经验,有两种方法可以解决此问题:

  1. Set a short-lived cookie on the download, and have JavaScript continually check for its existence. Only real issue is getting the cookie lifetime right - too short and the JS can miss it, too long and it might cancel the download screens for other downloads. Using JS to remove the cookie upon discovery usually fixes this.
  2. 使用fetch / XHR下载文件。您不仅确切地知道文件下载的完成时间,而且如果您使用XHR,还可以使用进度事件来显示进度栏!使用IE / Edge中的msSaveBlob保存生成的blob,在Firefox / Chrome中使用下载链接(如此链接)进行保存。这种方法的问题在于,iOS Safari似乎无法正确处理blob的下载-您可以使用FileReader将blob转换为数据URL,然后在新窗口中将其打开,但这将打开文件,而不是保存文件。
WT2020/03/12 20:36:21

如果您不想在服务器上生成和存储文件,是否愿意存储状态,例如,文件进行中,文件已完成?您的“等待”页面可能会轮询服务器以了解文件生成完成的时间。您可能不确定浏览器已开始下载,但您会有所信心。

西门达蒙2020/03/12 20:36:21

当用户触发文件生成时,您只需为该“下载”分配一个唯一的ID,然后将用户发送到每隔几秒钟刷新一次(或使用AJAX检查)的页面。文件完成后,将其保存在相同的唯一ID下并...

  • 如果文件已准备就绪,请进行下载。
  • 如果文件尚未准备好,请显示进度。

然后,您可以跳过整个iframe / waiting / browserwindow混乱的情况,但有一个非常优雅的解决方案。

eva2020/03/12 20:36:21

如果您正在流式传输要动态生成的文件,并且还实现了实时的服务器到客户端消息传递库,则可以非常轻松地向客户端发出警报。

我喜欢并推荐的服务器到客户端消息传递库是Socket.io(通过Node.js)。服务器脚本完成后,生成正在传输的文件以供下载,该脚本中的最后一行可以向Socket.io发出消息,该消息将通知发送给客户端。在客户端上,Socket.io侦听服务器发出的传入消息,并允许您对其进行操作。与其他方法相比,使用此方法的好处是您可以在流完成后检测到“ true”完成事件。

例如,您可以在单击下载链接后显示忙碌指示器,流传输文件,在流脚本的最后一行中从服务器向Socket.io发送消息,在客户端上侦听通知,接收通知并通过隐藏忙碌指示器来更新您的UI。

我意识到大多数阅读此问题答案的人可能没有这种类型的设置,但是我已经在我自己的项目中使用了这种精确的解决方案来产生巨大的效果,并且效果很好。

Socket.io非常易于安装和使用。查看更多:http : //socket.io/

十三蛋蛋2020/03/12 20:36:21

一种非常简单(且la脚)的单行解决方案是使用该window.onblur()事件关闭加载对话框。当然,如果花费的时间太长,并且用户决定执行其他操作(例如阅读电子邮件),则加载对话框将关闭。

乐理查德2020/03/12 20:36:21

一种可能的解决方案是在客户端上使用JavaScript。

客户端算法:

  1. 生成随机的唯一令牌。
  2. 提交下载请求,并将令牌包括在GET / POST字段中。
  3. 显示“正在等待”指示器。
  4. 启动一个计时器,然后每隔一秒钟左右,查找一个名为“ fileDownloadToken”(或您决定的内容)的cookie。
  5. 如果cookie存在,并且其值与令牌匹配,则隐藏“等待”指示符。

服务器算法:

  1. 在请求中查找GET / POST字段。
  2. 如果它具有非空值,则删除一个cookie(例如“ fileDownloadToken”),并将其值设置为令牌的值。

客户端源代码(JavaScript):

function getCookie( name ) {
  var parts = document.cookie.split(name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

function expireCookie( cName ) {
    document.cookie = 
        encodeURIComponent(cName) + "=deleted; expires=" + new Date( 0 ).toUTCString();
}

function setCursor( docStyle, buttonStyle ) {
    document.getElementById( "doc" ).style.cursor = docStyle;
    document.getElementById( "button-id" ).style.cursor = buttonStyle;
}

function setFormToken() {
    var downloadToken = new Date().getTime();
    document.getElementById( "downloadToken" ).value = downloadToken;
    return downloadToken;
}

var downloadTimer;
var attempts = 30;

// Prevents double-submits by waiting for a cookie from the server.
function blockResubmit() {
    var downloadToken = setFormToken();
    setCursor( "wait", "wait" );

    downloadTimer = window.setInterval( function() {
        var token = getCookie( "downloadToken" );

        if( (token == downloadToken) || (attempts == 0) ) {
            unblockSubmit();
        }

        attempts--;
    }, 1000 );
}

function unblockSubmit() {
  setCursor( "auto", "pointer" );
  window.clearInterval( downloadTimer );
  expireCookie( "downloadToken" );
  attempts = 30;
}

服务器代码示例(PHP):

$TOKEN = "downloadToken";

// Sets a cookie so that when the download begins the browser can
// unblock the submit button (thus helping to prevent multiple clicks).
// The false parameter allows the cookie to be exposed to JavaScript.
$this->setCookieToken( $TOKEN, $_GET[ $TOKEN ], false );

$result = $this->sendFile();

哪里:

public function setCookieToken(
    $cookieName, $cookieValue, $httpOnly = true, $secure = false ) {

    // See: http://stackoverflow.com/a/1459794/59087
    // See: http://shiflett.org/blog/2006/mar/server-name-versus-http-host
    // See: http://stackoverflow.com/a/3290474/59087
    setcookie(
        $cookieName,
        $cookieValue,
        2147483647,            // expires January 1, 2038
        "/",                   // your path
        $_SERVER["HTTP_HOST"], // your domain
        $secure,               // Use true over HTTPS
        $httpOnly              // Set true for $AUTH_COOKIE_NAME
    );
}