[javascript] 브라우저가 파일 다운로드를받는시기 감지

사용자가 동적으로 생성 된 파일을 다운로드 할 수있는 페이지가 있습니다. 생성하는 데 시간이 오래 걸리므로 “대기 중”표시기를 표시하고 싶습니다. 문제는 브라우저가 파일을받은 시점을 감지하는 방법을 알 수 없으므로 표시기를 숨길 수 있다는 것입니다.

숨겨진 양식으로 요청을 작성하여 서버에 POST하고 결과를 위해 숨겨진 iframe을 대상으로합니다. 이것은 전체 브라우저 창을 결과로 바꾸지 않습니다. iframe에서 “로드”이벤트를 수신합니다. 다운로드가 완료되면 실행되기를 바랍니다.

파일과 함께 “Content-Disposition : attachment”헤더를 반환하면 브라우저에 “저장”대화 상자가 표시됩니다. 그러나 브라우저는 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는 파일을 잘못된 이름과 내용 유형으로 다운로드하고 “로드”이벤트를 발생시키지 않습니다.

다른 방법은 파일 작성을 시작하기 위해 전화를 걸고 서버가 준비 될 때까지 폴링 한 다음 이미 작성된 파일을 다운로드하는 것입니다. 그러나 서버에서 임시 파일을 만드는 것을 피하고 싶습니다.

더 좋은 아이디어가 있습니까?



답변

하나의 가능한 솔루션 은 클라이언트에서 JavaScript를 사용합니다.

클라이언트 알고리즘 :

  1. 임의의 고유 토큰을 생성하십시오.
  2. 다운로드 요청을 제출하고 GET / POST 필드에 토큰을 포함 시키십시오.
  3. “대기 중”표시기를 표시하십시오.
  4. 타이머를 시작하고 약 1 초마다 “fileDownloadToken”(또는 결정한 것)이라는 쿠키를 찾으십시오.
  5. 쿠키가 존재하고 해당 값이 토큰과 일치하면 “대기 중”표시기를 숨 깁니다.

서버 알고리즘 :

  1. 요청에서 GET / POST 필드를 찾으십시오.
  2. 값이 비어 있지 않으면 쿠키 (예 : “fileDownloadToken”)를 삭제하고 해당 값을 토큰 값으로 설정하십시오.

클라이언트 소스 코드 (자바 스크립트) :

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


답변

매우 간단한 (그리고 절름발이) 한 줄 솔루션은 window.onblur()이벤트를 사용 하여 로딩 대화 상자를 닫는 것입니다. 물론 시간이 너무 오래 걸리고 사용자가 이메일 읽기와 같은 다른 작업을 수행하기로 결정하면 로딩 대화 상자가 닫힙니다.


답변

오래된 실, 알아

그러나 Google이 이끄는 것은 내 솔루션에 관심이있을 수 있습니다. 매우 간단하지만 신뢰할 수 있습니다. 실제 진행 메시지를 표시 할 수 있으며 기존 프로세스에 쉽게 연결할 수 있습니다.

처리하는 스크립트 (내 문제는 http를 통해 파일을 검색하여 zip으로 전달)가 세션에 상태를 기록합니다.

상태는 매초마다 폴링되고 표시됩니다. 그 전부입니다 (좋아요, 아닙니다. 많은 세부 사항 (예 : 동시 다운로드)을 처리해야하지만 시작하기에 좋은 곳입니다 ;-)).

다운로드 페이지 :

    <a href="download.php?id=1" class="download">DOWNLOAD 1</a>
    <a href="download.php?id=2" class="download">DOWNLOAD 2</a>
    ...
    <div id="wait">
    Please wait...
    <div id="statusmessage"></div>
    </div>
    <script>
//this is jquery
    $('a.download').each(function()
       {
        $(this).click(
             function(){
               $('#statusmessage').html('prepare loading...');
               $('#wait').show();
               setTimeout('getstatus()', 1000);
             }
          );
        });
    });
    function getstatus(){
      $.ajax({
          url: "/getstatus.php",
          type: "POST",
          dataType: 'json',
          success: function(data) {
            $('#statusmessage').html(data.message);
            if(data.status=="pending")
              setTimeout('getstatus()', 1000);
            else
              $('#wait').hide();
          }
      });
    }
    </script>

getstatus.php

<?php
session_start();
echo json_encode($_SESSION['downloadstatus']);
?>

download.php

    <?php
    session_start();
    $processing=true;
    while($processing){
      $_SESSION['downloadstatus']=array("status"=>"pending","message"=>"Processing".$someinfo);
      session_write_close();
      $processing=do_what_has_2Bdone();
      session_start();
    }
      $_SESSION['downloadstatus']=array("status"=>"finished","message"=>"Done");
//and spit the generated file to the browser
    ?>


답변

다음을 사용하여 Blob을 다운로드하고 다운로드 후 object-url을 취소하십시오. 크롬과 파이어 폭스에서 작동합니다!

function download(blob){
    var url = URL.createObjectURL(blob);
    console.log('create ' + url);

    window.addEventListener('focus', window_focus, false);
    function window_focus(){
        window.removeEventListener('focus', window_focus, false);
        URL.revokeObjectURL(url);
        console.log('revoke ' + url);
    }
    location.href = url;
}

파일 다운로드 대화 상자가 닫히면 창에서 포커스가 다시 돌아가서 포커스 이벤트가 트리거됩니다.


답변

Elmer의 예를 기반으로 내 솔루션을 준비했습니다. 정의 된 다운로드 클래스로 요소를 클릭 하면 화면에 사용자 정의 메시지를 표시 할 수 있습니다. 나는 초점을 사용했다 트리거를 하여 메시지를 숨겼습니다.

자바 스크립트

$(function(){$('.download').click(function() { ShowDownloadMessage(); }); })

function ShowDownloadMessage()
{
     $('#message-text').text('your report is creating, please wait...');
     $('#message').show();
     window.addEventListener('focus', HideDownloadMessage, false);
}

function HideDownloadMessage(){
    window.removeEventListener('focus', HideDownloadMessage, false);
    $('#message').hide();
}

HTML

<div id="message" style="display: none">
    <div id="message-screen-mask" class="ui-widget-overlay ui-front"></div>
    <div id="message-text" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-front ui-draggable ui-resizable waitmessage">please wait...</div>
</div>

이제 다운로드 할 요소를 구현해야합니다.

<a class="download" href="file://www.ocelot.com.pl/prepare-report">Download report</a>

또는

<input class="download" type="submit" value="Download" name="actionType">

다운로드를 클릭 할 때마다 보고서가 작성되고 있다는 메시지가 표시됩니다 . 잠시 기다려주십시오 …


답변

나는 까다로운 답변에 설명 된 것과 유사한 기술을 구현하는 간단한 JavaScript 클래스를 작성했습니다 . 나는 그것이 누군가에게 유용 할 수 있기를 바랍니다. GitHub 프로젝트를 response-monitor.js 라고합니다.

기본적으로 spin.js 를 대기 표시기로 사용하지만 사용자 지정 표시기 구현을위한 콜백 세트도 제공합니다.

JQuery는 지원되지만 필수는 아닙니다.

주목할만한 특징

  • 간단한 통합
  • 의존성 없음
  • JQuery 플러그인 (선택 사항)
  • Spin.js 통합 (선택 사항)
  • 이벤트 모니터링을위한 구성 가능한 콜백
  • 여러 개의 동시 요청 처리
  • 서버 측 오류 감지
  • 타임 아웃 감지
  • 크로스 브라우저

사용법 예

HTML

<!-- the response monitor implementation -->
<script src="response-monitor.js"></script>

<!-- optional JQuery plug-in -->
<script src="response-monitor.jquery.js"></script>

<a class="my_anchors" href="/report?criteria1=a&criteria2=b#30">Link 1 (Timeout: 30s)</a>
<a class="my_anchors" href="/report?criteria1=b&criteria2=d#10">Link 2 (Timeout: 10s)</a>

<form id="my_form" method="POST">
    <input type="text" name="criteria1">
    <input type="text" name="criteria2">
    <input type="submit" value="Download Report">
</form>

클라이언트 (일반 JavaScript)

//registering multiple anchors at once
var my_anchors = document.getElementsByClassName('my_anchors');
ResponseMonitor.register(my_anchors); //clicking on the links initiates monitoring

//registering a single form
var my_form = document.getElementById('my_form');
ResponseMonitor.register(my_form); //the submit event will be intercepted and monitored

클라이언트 (JQuery)

$('.my_anchors').ResponseMonitor();
$('#my_form').ResponseMonitor({timeout: 20});

콜백이있는 클라이언트 (JQuery)

//when options are defined, the default spin.js integration is bypassed
var options = {
    onRequest: function(token){
        $('#cookie').html(token);
        $('#outcome').html('');
        $('#duration').html('');
    },
    onMonitor: function(countdown){
        $('#duration').html(countdown);
    },
    onResponse: function(status){
        $('#outcome').html(status==1?'success':'failure');
    },
    onTimeout: function(){
        $('#outcome').html('timeout');
    }
};

//monitor all anchors in the document
$('a').ResponseMonitor(options);

서버 (PHP)

$cookiePrefix = 'response-monitor'; //must match the one set on the client options
$tokenValue = $_GET[$cookiePrefix];
$cookieName = $cookiePrefix.'_'.$tokenValue; //ex: response-monitor_1419642741528

//this value is passed to the client through the ResponseMonitor.onResponse callback
$cookieValue = 1; //for ex, "1" can interpret as success and "0" as failure

setcookie(
    $cookieName,
    $cookieValue,
    time()+300,            // expire in 5 minutes
    "/",
    $_SERVER["HTTP_HOST"],
    true,
    false
);

header('Content-Type: text/plain');
header("Content-Disposition: attachment; filename=\"Response.txt\"");

sleep(5); //simulate whatever delays the response
print_r($_REQUEST); //dump the request in the text file

더 많은 예제를 보려면 리포지토리 의 예제 폴더를 확인하십시오 .


답변

나는 파티에 매우 늦었지만 다른 사람이 내 솔루션을 알고 싶다면 여기에 올려 놓을 것입니다.

나는이 정확한 문제로 실제로 어려움을 겪었지만 iframe을 사용하여 실행 가능한 솔루션을 찾았습니다 (나도 알고 있습니다.

파일을 생성 한 다음 다운로드 한 별도의 PHP 스크립트를 시작하는 html 페이지가 있습니다. html 페이지에서 html 헤더에 다음 jquery를 사용했습니다 (jquery 라이브러리도 포함해야 함).

<script>
    $(function(){
        var iframe = $("<iframe>", {name: 'iframe', id: 'iframe',}).appendTo("body").hide();
        $('#click').on('click', function(){
            $('#iframe').attr('src', 'your_download_script.php');
        });
        $('iframe').load(function(){
            $('#iframe').attr('src', 'your_download_script.php?download=yes'); <!--on first iframe load, run script again but download file instead-->
            $('#iframe').unbind(); <!--unbinds the iframe. Helps prevent against infinite recursion if the script returns valid html (such as echoing out exceptions) -->
        });
    });
</script>

your_download_script.php에서 다음을 갖추십시오.

function downloadFile($file_path) {
    if (file_exists($file_path)) {
        header('Content-Description: File Transfer');
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename=' . basename($file_path));
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file_path));
        ob_clean();
        flush();
        readfile($file_path);
        exit();
    }
}


$_SESSION['your_file'] = path_to_file; //this is just how I chose to store the filepath

if (isset($_REQUEST['download']) && $_REQUEST['download'] == 'yes') {
    downloadFile($_SESSION['your_file']);
} else {
    *execute logic to create the file*
}

이 문제를 해결하기 위해 jquery는 먼저 iframe에서 PHP 스크립트를 시작합니다. 파일이 생성되면 iframe이로드됩니다. 그런 다음 jquery는 스크립트에 파일을 다운로드하도록 요청하는 요청 변수와 함께 스크립트를 다시 시작합니다.

다운로드 및 파일 생성을 한 번에 수행 할 수없는 이유는 php header () 함수 때문입니다. header ()를 사용하는 경우 스크립트를 웹 페이지가 아닌 다른 것으로 변경하면 jquery는 다운로드 스크립트가 ‘로드’된 것으로 인식하지 않습니다. 브라우저가 파일을 수신 할 때 이것이 반드시 감지되지는 않을 수도 있지만 문제는 내 것과 비슷하다고 들었습니다.