1. ホーム
  2. ジャバスクリプト

[解決済み] ブラウザがファイルのダウンロードを受信したことを検出する

2022-05-02 17:02:38

質問

動的に生成されたファイルをユーザーがダウンロードできるページがあります。生成に時間がかかるので、"waiting"のインジケータを表示したいのですが、どうすればよいでしょうか?問題は、ブラウザがファイルを受信したことを検知して、インジケータを非表示にする方法がわからないことです。

非表示のフォームを要求しているのですが、これは POST をサーバーに送信し、その結果を隠しiframeをターゲットにします。これは、ブラウザーのウィンドウ全体を結果で置き換えないようにするためです。ダウンロードが完了したときに、iframeの"load"イベントが発生することを期待して、イベントを待機しています。

を返します。 Content-Disposition: attachment ヘッダをファイルと共に表示し、ブラウザに "保存"ダイアログを表示させます。しかし、ブラウザはiframeで"load"イベントを発生させません。

私が試した方法の1つは multi-part のレスポンスがあります。つまり、空の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ファイルを受信して "load" イベントが発生し "Save" ダイアログが表示されます。しかし インターネットエクスプローラ サファリ Internet Explorer は "load"イベントを発生させますが、ファイルはダウンロードされません。 Safariがダウンロードする は発生せず、(間違った名前とコンテントタイプの)ファイルが表示されます。 "ロード" イベントが発生します。

別のアプローチとして、ファイル作成を開始するために呼び出し、準備が整うまでサーバーをポーリングし、その後、既に作成されたファイルをダウンロードすることも可能かもしれません。しかし、私はむしろサーバーに一時ファイルを作成するのを避けたいのです。

どうすればいいのでしょうか?

解決するには?

可能な解決策 は、クライアント側でJavaScriptを使用します。

クライアントのアルゴリズムです。

  1. ランダムなユニークトークンを生成します。
  2. ダウンロード要求を送信し、GET/POSTフィールドにトークンを含めます。
  3. waiting"インジケータを表示します。
  4. タイマーを起動し、約1秒ごとに "fileDownloadToken" という名前のクッキーを探します(またはあなたが決めたもの)。
  5. クッキーが存在し、その値がトークンに一致する場合、"waiting"インジケータを非表示にします。

サーバーのアルゴリズムです。

  1. リクエストの中からGET/POSTフィールドを探します。
  2. 空でない値を持つ場合、クッキー(例:"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
    );
}