programing

브라우저가 파일 다운로드를 수신할 때 감지

yoursource 2022. 9. 17. 22:29
반응형

브라우저가 파일 다운로드를 수신할 때 감지

사용자가 동적으로 생성된 파일을 다운로드할 수 있는 페이지가 있습니다.생성에 시간이 오래 걸리기 때문에 대기 표시기를 보여드리고 싶습니다.문제는 브라우저가 파일을 수신했을 때 인디케이터를 숨길 수 있도록 검출하는 방법을 알 수 없다는 것입니다.

숨김 폼을 요청합니다.서버에 투고하여 숨겨진 iframe을 대상으로 합니다.따라서 브라우저 창 전체를 결과로 대체하지 않습니다.다운로드가 완료되면 부팅되기를 바라며 iframe에서 "load" 이벤트를 듣습니다.

Content-Disposition: attachment. 헤더를 파일로 지정합니다.이것에 의해, 브라우저에 「Save」대화상자가 표시됩니다.그러나 브라우저는 iframe에서 "로드" 이벤트를 발생시키지 않습니다.

제가 중 하나는 '아예'를 입니다.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와 Safari에서는 실패합니다.Internet Explorer는 "로드" 이벤트를 발생시키지만 파일을 다운로드하지 않으며, Safari는 잘못된 이름과 컨텐츠 유형의 파일을 다운로드하여 "로드" 이벤트를 발생시키지 않습니다.

다른 접근법은 를 호출하여 파일 작성을 시작하고 서버가 준비될 때까지 폴링한 후 이미 작성된 파일을 다운로드하는 것입니다.하지만 서버에 임시 파일을 만드는 것은 피하고 싶습니다.

어떻게 해야 하나?

클라이언트 상에서 JavaScript를 사용하는 솔루션도 있습니다.

클라이언트 알고리즘:

  1. 랜덤 고유 토큰을 생성합니다.
  2. 다운로드 요청을 전송하고 GET/POST 필드에 토큰을 포함합니다.
  3. 대기 표시기를 표시합니다.
  4. 타이머를 켜고 매초 "fileDownload"라는 이름의 쿠키를 찾습니다.토큰" (또는 원하는 대로)
  5. 쿠키가 존재하고 해당 값이 토큰과 일치하는 경우 "대기 중" 표시기를 숨깁니다.

서버 알고리즘:

  1. 요청에서 GET/POST 필드를 찾습니다.
  2. 값이 비어 있지 않으면 쿠키를 드롭합니다(예: fileDownload).토큰") 및 토큰 값을 설정합니다.

클라이언트 소스 코드(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
    );
}

심플한(및 레임)의 솔루션은, 「」를 입니다.window.onblur()벤 - - - 로를를를를를 다다다다다다물론 시간이 너무 오래 걸리고 사용자가 다른 작업(이메일 읽기 등)을 수행하기로 결정하면 로드 대화 상자가 닫힙니다.

이 솔루션은 매우 간단하지만 신뢰성이 높습니다.또한 실제 진행 상황 메시지를 표시할 수 있습니다(기존 프로세스에 쉽게 연결할 수 있습니다).

처리하는 스크립트(문제는 HTTP를 통해 파일을 가져와 ZIP으로 전달)가 세션에 상태를 기록합니다.

상태는 폴링되어 매초 표시됩니다.이상입니다(OK, 아닙니다).동시 다운로드 등 많은 세부 정보를 관리해야 하지만, 시작하기에 좋습니다.

다운로드 페이지:

<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']);
?>

파일 다운로드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
?>

문제의 핵심은 웹 브라우저에는 페이지네비게이션이 취소되었을 때 발생하는 이벤트가 없지만 페이지 로딩이 완료되었을 때 발생하는 이벤트가 있다는 것입니다.다이렉트 브라우저 이벤트 이외의 이벤트는 찬반 양론이 있는 해킹이 될 것입니다.

브라우저 다운로드 시작 시 검출에 대처하기 위해서는 다음 4가지 방법이 있습니다.

  1. 를 응답 하고 fetch()를 합니다.a를 달다download이치노최신 웹 브라우저에서는 이미 검색된 파일을 저장할 수 있는 옵션이 제공됩니다.접근법에는 몇 이 있습니다. 즉, 단점은 다음과 같습니다.
  • 데이터 블록 전체가 RAM에 저장되기 때문에 파일이 크면 그만큼 RAM을 소비하게 됩니다.작은 파일의 경우, 이것은 거래 위반이 되지 않을 것입니다.
  • 사용자는 파일을 저장하기 전에 파일 전체가 다운로드될 때까지 기다려야 합니다.또한 완료될 때까지 페이지를 떠날 수 없습니다.
  • 내장된 웹 브라우저 파일다운로더는 사용되지 않습니다.
  • 크로스 도메인 취득은, CORS 헤더가 설정되어 있지 않는 한 실패합니다.
  1. + a cookieiframe + 버버 use use use use use use use use use use use use use use use use use use use use use use use use 。은 iframe을 합니다.loadiframe 이벤트 그러나 다운로드가 시작되어도 이벤트는 발생하지 않습니다.자바스크립트접근법에는 몇 이 있습니다. 즉, 단점은 다음과 같습니다.
  • 서버와 클라이언트는 함께 작업해야 합니다.서버가 쿠키를 설정해야 합니다.클라이언트는 쿠키를 검출해야 합니다.
  • 교차 도메인 요청은 쿠키를 설정할 수 없습니다.
  • 도메인당 설정할 수 있는 쿠키 수에는 제한이 있습니다.
  • 사용자 지정 HTTP 헤더를 보낼 수 없습니다.
  1. URL 이이이r iframe url url 。에 의해 후에 .iframe은 HTML의 URL 입니다. 그러면 1초 후에 다운로드가 트리거됩니다.loadloads.iframe의 HTML에서 합니다.접근법에는 몇 이 있습니다. 즉, 단점은 다음과 같습니다.
  • 서버는 다운로드되는 콘텐츠의 스토리지를 유지해야 합니다.디렉토리를 정기적으로 청소하려면 cron 작업 또는 이와 유사한 작업이 필요합니다.
  • 파일이 준비되면 서버는 특별한 HTML 콘텐츠를 덤프해야 합니다.
  • 클라이언트는 DOM에서 iframe을 삭제하기 전에 iframe이 서버에 실제로 두 번째 요구를 한 시기와 다운로드가 실제로 시작된 시점을 추측해야 합니다.이것은, iframe을 DOM 에 남겨 두는 것만으로 해결할 수 있습니다.
  • 사용자 지정 HTTP 헤더를 보낼 수 없습니다.
  1. iframe + XHR 을 사용합니다.iframe은 다운로드 요청을 트리거합니다.iframe을 통해 요구가 이루어지면 XHR을 통해 동일한 요구가 이루어집니다.이 경우,load iframe.iframe, XHR, iframe, iframe을 합니다. XHR의 progress이벤트가 기동하면 iframe에서 다운로드가 시작되고 XHR 요구를 중단하고 몇 초간 기다린 후 iframe을 삭제합니다.이것에 의해, 서버측의 쿠키에 의존하지 않고, 대용량의 파일을 다운로드할 수 있습니다.접근법에는 몇 이 있습니다. 즉, 단점은 다음과 같습니다.
  • 동일한 정보에 대한 두 가지 개별 요청이 있습니다.서버는 착신 헤더를 체크함으로써 XHR과 iframe을 구별할 수 있습니다.
  • 크로스 도메인 XHR 요구는, CORS 헤더가 설정되어 있지 않는 한 실패할 가능성이 있습니다.그러나 서버가 HTTP 헤더를 반환할 때까지 브라우저는 CORS 허용 여부를 알 수 없습니다.파일 데이터가 준비될 때까지 서버가 헤더 전송을 대기하면 XHR은 CORS가 없어도 iframe 다운로드가 언제 시작되었는지 대략적으로 검출할 수 있습니다.
  • 클라이언트는 다운로드가 실제로 언제 시작되어 DOM에서 iframe이 삭제되었는지 추측해야 합니다.이것은, iframe을 DOM 에 남겨 두는 것만으로 해결할 수 있습니다.
  • iframe에 사용자 지정 헤더를 보낼 수 없습니다.

적절한 내장 웹 브라우저 이벤트가 없으면 완벽한 솔루션은 없습니다.그러나 사용 사례에 따라 위의 네 가지 방법 중 하나가 다른 방법보다 더 적합할 수 있습니다.

가능하면 서버에서 모든 것을 먼저 생성한 후 응답을 보내는 대신 클라이언트에 즉시 응답을 스트리밍하십시오.CSV, JSON, XML, ZIP 등 다양한 파일 형식을 스트리밍할 수 있습니다.스트리밍 콘텐츠를 지원하는 라이브러리를 찾는 것이 중요합니다.요청이 시작되자마자 응답을 스트리밍할 때 다운로드 시작은 거의 즉시 시작되므로 감지하는 것은 그다지 중요하지 않습니다.

다른 옵션은 다운로드 헤더를 먼저 출력하는 것입니다.모든 콘텐츠가 먼저 생성되기를 기다리지 않고 다운로드 헤더를 먼저 출력하는 것입니다.그런 다음 콘텐츠를 생성하고 마지막으로 클라이언트로 전송을 시작합니다.사용자의 기본 제공 다운로더는 데이터가 도착하기 시작할 때까지 끈기 있게 기다립니다.단점은 (클라이언트 측 또는 서버 측) 데이터 흐름을 기다리는 동안 기본 네트워크 연결이 타임아웃될 수 있다는 것입니다.

엘머의 사례를 바탕으로 저는 저만의 해결책을 준비했습니다.정의된 "다운로드" 클래스가 있는 항목을 클릭하면 브라우저 창에 사용자 지정 메시지가 표시됩니다.메시지를 숨기려고 포커스 트리거를 사용했어메시지를 숨기기 위해 포커스 트리거를 사용했어

자바스크립트

$(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">

다운로드를 클릭할 마다 다음 메시지가 나타납니다.
보고서를 만들고 있습니다.기다려 주세요...

다운로드 후 BLOB를 다운로드하고 오브젝트 URL을 해지하려면 다음을 사용합니다.Chrome과 Firefox에서 작동합니다!

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

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

그 설정에서도 같은 문제에 직면했습니다.

쿠키를 사용한 솔루션:

클라이언트 측:

폼을 제출할 때 JavaScript 함수를 호출하여 페이지를 숨기고 대기 중인 스피너를 로드합니다.

function loadWaitingSpinner() {
    ... hide your page and show your spinner ...
}

그런 다음 cookie가 서버에서 전송되는지 여부를 500ms마다 확인하는 함수를 호출합니다.

function checkCookie() {
    var verif = setInterval(isWaitingCookie, 500, verif);
}

cookie가 발견되면 500ms마다 체크를 중지하고 cookie를 종료하고 함수를 호출하여 페이지로 돌아가 대기 중인 스피너(removeWaitingSpinner())를 삭제합니다.다른 파일을 다시 다운로드하려면 쿠키를 만료해야 합니다.

function isWaitingCookie(verif) {
    var loadState = getCookie("waitingCookie");
    if (loadState == "done") {
        clearInterval(verif);
        document.cookie = "attenteCookie=done; expires=Tue, 31 Dec 1985 21:00:00 UTC;";
        removeWaitingSpinner();
    }
}

function getCookie(cookieName) {
    var name = cookieName + "=";
    var cookies = document.cookie
    var cs = cookies.split(';');
    for (var i = 0; i < cs.length; i++) {
        var c = cs[i];
        while(c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

function removeWaitingSpinner() {
    ... come back to your page and remove your spinner ...
}

서버 측:

서버 프로세스가 끝나면 응답에 쿠키를 추가합니다.이 쿠키는 파일을 다운로드할 준비가 되면 클라이언트에 전송됩니다.

Cookie waitCookie = new Cookie("waitingCookie", "done");
response.addCookie(waitCookie);

동적으로 생성하는 파일을 스트리밍하고 실시간 서버-클라이언트 메시징 라이브러리를 구현한 경우 클라이언트에 쉽게 알릴 수 있습니다.

서버 간 메시징 라이브러리는 Socket.io (Node.js 경유)입니다.서버 스크립트가 완료되면 해당 스크립트의 마지막 행을 다운로드하기 위해 스트리밍 중인 파일이 Socket.io으로 메시지를 전송하고 클라이언트에 알림을 보냅니다.클라이언트에서는, Socket.io 는 서버로부터 발신된 착신 메세지를 리슨 해, 유저가 그 메세지에 대응할 수 있도록 합니다.다른 방법보다 이 방법을 사용하면 스트리밍이 완료된 후 "진정한" 종료 이벤트를 감지할 수 있다는 장점이 있습니다.

예를 들어 다운로드 링크를 클릭한 후 비지 인디케이터를 표시하고 파일을 스트리밍하고 스트리밍 스크립트의 마지막 줄에 있는 서버에서 Socket.io으로 메시지를 전송하며 클라이언트의 알림을 듣고 알림을 수신하고 비지 인디케이터를 숨김으로써 UI를 업데이트할 수 있습니다.

이 질문에 대한 답을 읽는 대부분의 사람들은 이런 종류의 설정을 가지고 있지 않을 수 있다는 것을 알고 있습니다. 하지만 저는 이 솔루션을 제 프로젝트에서 큰 효과를 거두기 위해 사용했습니다. 그리고 그것은 훌륭하게 작동합니다.

Socket.io은 설치와 사용이 매우 간단합니다.상세보기 : http://socket.io/

Bultorious' answer에서 설명한 것과 유사한 기술을 구현하는 간단한 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; // Example: response-monitor_1419642741528

// This value is passed to the client through the ResponseMonitor.onResponse callback
$cookieValue = 1; // For example, "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

자세한 예는 저장소의 예제 폴더를 참조하십시오.

다른 곳에서 가져온 솔루션:

/**
 *  download file, show modal
 *
 * @param uri link
 * @param name file name
 */
function downloadURI(uri, name) {
// <------------------------------------------       Do something (show loading)
    fetch(uri)
        .then(resp => resp.blob())
        .then(blob => {
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = url;
            // the filename you want
            a.download = name;
            document.body.appendChild(a);
            a.click();
            window.URL.revokeObjectURL(url);
            // <----------------------------------------  Detect here (hide loading)
            alert('File detected');
        })
        .catch(() => alert('An error sorry'));
}

사용할 수 있습니다.

downloadURI("www.linkToFile.com", "file.name");

저는 이 문제에 대해 정말 고심했지만, 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는 다운로드 스크립트를 '로드'된 것으로 인식하지 않습니다.브라우저가 파일을 수신했을 때 이것이 반드시 검출되는 것은 아니라는 것을 알지만, 당신의 문제는 저와 비슷하게 들립니다.

사용자가 파일 생성을 트리거하면 해당 "다운로드"에 고유 ID를 할당하고 사용자를 몇 초마다 새로 고침(또는 AJAX에서 확인)하는 페이지로 보낼 수 있습니다.파일이 완료되면 동일한 고유 ID로 저장합니다.

  • 파일이 준비되면 다운로드를 수행합니다.
  • 파일이 준비되지 않은 경우 진행 상황을 표시합니다.

그러면 iframe/waiting/browserwindow의 혼란은 생략하고 매우 우아한 솔루션을 사용할 수 있습니다.

파일을 생성 및 서버에 저장하지 않으려면 file-in-progress, file-complete 등의 상태를 저장하시겠습니까?「대기중」페이지에서는, 서버의 폴링을 실시해, 파일 생성이 완료되는 것을 알 수 있습니다.브라우저가 다운로드를 시작했는지 확실히 알 수는 없지만 어느 정도 자신 있습니다.

제 경험상 이 문제에 대처하는 방법은 두 가지가 있습니다.

  1. 다운로드 시 짧은 cookie를 설정하고 JavaScript가 지속적으로 cookie의 존재를 확인하도록 합니다.유일한 진짜 문제는 쿠키의 수명을 올바르게 하는 것입니다. - 너무 짧고 자바스크립트가 너무 오래 놓칠 수 있으며 다른 다운로드를 위해 다운로드 화면이 취소될 수 있습니다.일반적으로 검색 시 JavaScript를 사용하여 쿠키를 제거하면 이 문제가 해결됩니다.
  2. fetch/X를 사용하여 파일 다운로드HR. 파일 다운로드가 언제 완료되는지 정확히 알 수 있을 뿐만 아니라 XHR을 사용하면 진행률 이벤트를 사용하여 진행률 표시줄을 표시할 수 있습니다.Internet Explorer 또는 EdgemsSaveBlob과 Firefox 및 Chrome의 다운로드 링크를 사용하여 결과 BLOB를 저장합니다.이 방법의 문제는 iOS Safari가 BLOB를 제대로 다운로드하지 못한다는 것입니다. FileReader를 사용하여 BLOB를 데이터 URL로 변환하여 새 창에서 열 수 있지만, 파일을 저장하지 않고 열 수 있습니다.

나도 똑같은 문제가 있었어이미 많은 임시 파일을 생성하고 있었기 때문에 임시 파일을 사용하는 것이 해결책이었습니다.양식은 다음 항목과 함께 제출됩니다.

var microBox = {
    show : function(content) {
        $(document.body).append('<div id="microBox_overlay"></div><div id="microBox_window"><div id="microBox_frame"><div id="microBox">' +
        content + '</div></div></div>');
        return $('#microBox_overlay');
    },

    close : function() {
        $('#microBox_overlay').remove();
        $('#microBox_window').remove();
    }
};

$.fn.bgForm = function(content, callback) {
    // Create an iframe as target of form submit
    var id = 'bgForm' + (new Date().getTime());
    var $iframe = $('<iframe id="' + id + '" name="' + id + '" style="display: none;" src="about:blank"></iframe>')
        .appendTo(document.body);
    var $form = this;
    // Submittal to an iframe target prevents page refresh
    $form.attr('target', id);
    // The first load event is called when about:blank is loaded
    $iframe.one('load', function() {
        // Attach listener to load events that occur after successful form submittal
        $iframe.load(function() {
            microBox.close();
            if (typeof(callback) == 'function') {
                var iframe = $iframe[0];
                var doc = iframe.contentWindow.document;
                var data = doc.body.innerHTML;
                callback(data);
            }
        });
    });

    this.submit(function() {
        microBox.show(content);
    });

    return this;
};

$('#myForm').bgForm('Please wait...');

가지고 있는 파일을 생성하는 스크립트의 마지막에 다음과 같이 표시됩니다.

header('Refresh: 0;url=fetch.php?token=' . $token);
echo '<html></html>';

이로 인해 iframe의 부하 이벤트가 발생합니다.그러면 대기 메시지가 닫히고 파일 다운로드가 시작됩니다.Internet Explorer 7 및 Firefox에서 테스트되었습니다.

문서가 아닌 저장된 파일을 다운로드한 경우 다운로드가 완료되었는지 확인할 수 있는 방법은 없습니다. 다운로드가 완료된 것은 현재 문서의 범위가 아니라 브라우저에서 별도의 프로세스이기 때문입니다.

브라우저의 캐시에 의존하여 파일이 캐시에 로드될 때 동일한 파일의 두 번째 다운로드를 트리거할 수 있습니다.

$('#link').click(function(e) {
    e.preventDefault();

    var url = $(this).attr('href');
    var request = new XMLHttpRequest();
    request.responseType = "blob";
    request.open("GET", url);

    var self = this;
    request.onreadystatechange = function () {
        if (request.readyState === 4) {
            var file = $(self).data('file');
            var anchor = document.createElement('a');
            anchor.download = file;
            console.log(file);
            console.log(request);
            anchor.href = window.URL.createObjectURL(request.response);
            anchor.click();
            console.log('Completed. Download window popped up.');
        }
    };
    request.send();
});

문제는 파일이 생성되는 동안 '대기 중' 표시기가 있고 파일을 다운로드하면 정상으로 돌아오는 것입니다.제가 좋아하는 방법은 숨겨진 iFrame을 사용하여 프레임의 온로드 이벤트를 잠그고 다운로드가 시작되면 제 페이지에 알리는 것입니다.

그러나 Internet Explorer에서 파일 다운로드에 대한 온로드는 실행되지 않습니다(첨부 헤더 토큰 등).서버를 폴링하는 것은 가능하지만, 저는 복잡함을 더하는 것을 싫어합니다.제가 하는 일은 다음과 같습니다.

  • 평소처럼 숨겨진 iFrame을 대상으로 합니다.
  • 콘텐츠를 생성합니다.2분 후 절대 타임아웃으로 캐시합니다.
  • JavaScript 리다이렉트를 발신측 클라이언트에 반송합니다.이 경우 기본적으로 제너레이터 페이지를 두 번째로 호출합니다.주의: Internet Explorer는 일반 페이지와 같이 작동하므로 이 경우 로드 이벤트가 발생합니다.
  • 캐시에서 콘텐츠를 삭제하여 클라이언트에 전송합니다.

면책사항:캐싱이 누적될 수 있으므로 사용량이 많은 사이트에서는 이 작업을 수행하지 마십시오.그러나 실제로 사이트가 그렇게 혼잡할 경우 오랜 시간 동안 실행되는 프로세스로 인해 스레드가 부족해질 수 있습니다.

코드 배후에 있는 것이, 필요한 것은 이것뿐입니다.

public partial class Download : System.Web.UI.Page
{
    protected System.Web.UI.HtmlControls.HtmlControl Body;

    protected void Page_Load( object sender, EventArgs e )
    {
        byte[ ] data;
        string reportKey = Session.SessionID + "_Report";

        // Check is this page request to generate the content
        //    or return the content (data query string defined)
        if ( Request.QueryString[ "data" ] != null )
        {
            // Get the data and remove the cache
            data = Cache[ reportKey ] as byte[ ];
            Cache.Remove( reportKey );

            if ( data == null )
                // send the user some information
                Response.Write( "Javascript to tell user there was a problem." );
            else
            {
                Response.CacheControl = "no-cache";
                Response.AppendHeader( "Pragma", "no-cache" );
                Response.Buffer = true;

                Response.AppendHeader( "content-disposition", "attachment; filename=Report.pdf" );
                Response.AppendHeader( "content-size", data.Length.ToString( ) );
                Response.BinaryWrite( data );
            }
            Response.End();
        }
        else
        {
            // Generate the data here. I am loading a file just for an example
            using ( System.IO.FileStream stream = new System.IO.FileStream( @"C:\1.pdf", System.IO.FileMode.Open ) )
                using ( System.IO.BinaryReader reader = new System.IO.BinaryReader( stream ) )
                {
                    data = new byte[ reader.BaseStream.Length ];
                    reader.Read( data, 0, data.Length );
                }

            // Store the content for retrieval
            Cache.Insert( reportKey, data, null, DateTime.Now.AddMinutes( 5 ), TimeSpan.Zero );

            // This is the key bit that tells the frame to reload this page
            //   and start downloading the content. NOTE: Url has a query string
            //   value, so that the content isn't generated again.
            Body.Attributes.Add("onload", "window.location = 'binary.aspx?data=t'");
        }
    }

다운로드 대화 상자가 표시될 때까지 메시지 또는 로더 GIF 이미지만 표시하려면 메시지를 숨겨진 컨테이너에 넣고 다운로드할 파일을 생성하는 버튼을 클릭하면 컨테이너가 표시됩니다.

그런 다음 jQuery 또는 JavaScript를 사용하여 단추의 포커스아웃 이벤트를 포착하여 메시지를 포함하는 컨테이너를 숨깁니다.

BLOB가 있는 XMLHttpRequest가 옵션이 아닌 경우 새 창에서 파일을 열고 해당 창 본문에 간격별로 요소가 채워져 있는지 확인할 수 있습니다.

var form = document.getElementById("frmDownlaod");
form.setAttribute("action", "downoad/url");
form.setAttribute("target", "downlaod");
var exportwindow = window.open("", "downlaod", "width=800,height=600,resizable=yes");
form.submit();

var responseInterval = setInterval(function() {
    var winBody = exportwindow.document.body
    if(winBody.hasChildNodes()) // Or 'downoad/url' === exportwindow.document.location.href
    {
        clearInterval(responseInterval);
        // Do your work.
        // If there is an error page configured in your application
        // for failed requests, check for those DOM elements.
    }
}, 1000)
// Better if you specify the maximum number of intervals

이 Java/Spring 예는 다운로드의 끝을 검출하고, 이 시점에서 "Loading.."을 숨깁니다." 표시기.

접근법: JavaScript 측에서 쿠키 유효기간을 최대 2분으로 설정하고 쿠키 유효기간을 초당 폴링합니다.그런 다음 서버 측은 서버 프로세스가 완료된 이전 만료 기간으로 이 쿠키를 재정의합니다.JavaScript 폴링에서 쿠키 만료가 감지되면 바로 "Loading..「」는 숨겨져 있습니다.

JavaScript 측

function buttonClick() { // Suppose this is the handler for the button that starts
    $("#loadingProgressOverlay").show();  // Show loading animation
    startDownloadChecker("loadingProgressOverlay", 120);
    // Here you launch the download URL...
    window.location.href = "myapp.com/myapp/download";
}

// This JavaScript function detects the end of a download.
// It does timed polling for a non-expired Cookie, initially set on the
// client-side with a default max age of 2 min.,
// but then overridden on the server-side with an *earlier* expiration age
// (the completion of the server operation) and sent in the response.
// Either the JavaScript timer detects the expired cookie earlier than 2 min.
// (coming from the server), or the initial JavaScript-created cookie expires after 2 min.
function startDownloadChecker(imageId, timeout) {

    var cookieName = "ServerProcessCompleteChecker";  // Name of the cookie which is set and later overridden on the server
    var downloadTimer = 0;  // Reference to the timer object

    // The cookie is initially set on the client-side with a specified default timeout age (2 min. in our application)
    // It will be overridden on the server side with a new (earlier) expiration age (the completion of the server operation),
    // or auto-expire after 2 min.
    setCookie(cookieName, 0, timeout);

    // Set a timer to check for the cookie every second
    downloadTimer = window.setInterval(function () {

        var cookie = getCookie(cookieName);

        // If cookie expired (NOTE: this is equivalent to cookie "doesn't exist"), then clear "Loading..." and stop polling
        if ((typeof cookie === 'undefined')) {
            $("#" + imageId).hide();
            window.clearInterval(downloadTimer);
        }

    }, 1000); // Every second
}

// These are helper JavaScript functions for setting and retrieving a Cookie
function setCookie(name, value, expiresInSeconds) {
    var exdate = new Date();
    exdate.setTime(exdate.getTime() + expiresInSeconds * 1000);
    var c_value = escape(value) + ((expiresInSeconds == null) ? "" : "; expires=" + exdate.toUTCString());
    document.cookie = name + "=" + c_value + '; path=/';
}

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

Java/Spring 서버 측

    @RequestMapping("/download")
    public String download(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //... Some logic for downloading, returning a result ...

        // Create a Cookie that will override the JavaScript-created
        // Max-Age-2min Cookie with an earlier expiration (same name)
        Cookie myCookie = new Cookie("ServerProcessCompleteChecker", "-1");
        myCookie.setMaxAge(0); // This is immediate expiration, but can also
                               // add +3 seconds for any flushing concerns
        myCookie.setPath("/");
        response.addCookie(myCookie);
        //... -- presumably the download is writing to the Output Stream...
        return null;
}

PrimeFaces도 쿠키 폴링을 사용합니다.

monitor Download():

    monitorDownload: function(start, complete, monitorKey) {
        if(this.cookiesEnabled()) {
            if(start) {
                start();
            }

            var cookieName = monitorKey ? 'primefaces.download_' + monitorKey : 'primefaces.download';
            window.downloadMonitor = setInterval(function() {
                var downloadComplete = PrimeFaces.getCookie(cookieName);

                if(downloadComplete === 'true') {
                    if(complete) {
                        complete();
                    }
                    clearInterval(window.downloadMonitor);
                    PrimeFaces.setCookie(cookieName, null);
                }
            }, 1000);
        }
    },

아래 참조 코드를 업데이트했습니다.적절한 다운로드 URL 링크를 추가하여 사용해 보십시오.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <style type="text/css">
            body {
                padding: 0;
                margin: 0;
            }

            svg:not(:root) {
                display: block;
            }

            .playable-code {
                background-color: #F4F7F8;
                border: none;
                border-left: 6px solid #558ABB;
                border-width: medium medium medium 6px;
                color: #4D4E53;
                height: 100px;
                width: 90%;
                padding: 10px 10px 0;
            }

            .playable-canvas {
                border: 1px solid #4D4E53;
                border-radius: 2px;
            }

            .playable-buttons {
                text-align: right;
                width: 90%;
                padding: 5px 10px 5px 26px;
            }
        </style>

        <style type="text/css">
            .event-log {
                width: 25rem;
                height: 4rem;
                border: 1px solid black;
                margin: .5rem;
                padding: .2rem;
            }

            input {
                width: 11rem;
                margin: .5rem;
            }

        </style>

        <title>XMLHttpRequest: progress event - Live_example - code sample</title>
    </head>

    <body>
        <div class="controls">
            <input class="xhr success" type="button" name="xhr" value="Click to start XHR (success)" />
            <input class="xhr error" type="button" name="xhr" value="Click to start XHR (error)" />
            <input class="xhr abort" type="button" name="xhr" value="Click to start XHR (abort)" />
        </div>

        <textarea readonly class="event-log"></textarea>

        <script>
            const xhrButtonSuccess = document.querySelector('.xhr.success');
            const xhrButtonError = document.querySelector('.xhr.error');
            const xhrButtonAbort = document.querySelector('.xhr.abort');
            const log = document.querySelector('.event-log');

            function handleEvent(e) {
                if (e.type == 'progress')
                {
                    log.textContent = log.textContent + `${e.type}: ${e.loaded} bytes transferred Received ${event.loaded} of ${event.total}\n`;
                }
                else if (e.type == 'loadstart')
                {
                    log.textContent = log.textContent + `${e.type}: started\n`;
                }
                else if  (e.type == 'error')
                {
                    log.textContent = log.textContent + `${e.type}: error\n`;
                }
                else if (e.type == 'loadend')
                {
                    log.textContent = log.textContent + `${e.type}: completed\n`;
                }
            }

            function addListeners(xhr) {
                xhr.addEventListener('loadstart', handleEvent);
                xhr.addEventListener('load', handleEvent);
                xhr.addEventListener('loadend', handleEvent);
                xhr.addEventListener('progress', handleEvent);
                xhr.addEventListener('error', handleEvent);
                xhr.addEventListener('abort', handleEvent);
            }

            function runXHR(url) {
                log.textContent = '';

                const xhr = new XMLHttpRequest();

                var request = new XMLHttpRequest();
                addListeners(request);
                request.open('GET', url, true);
                request.responseType = 'blob';
                request.onload = function (e) {
                    var data = request.response;
                    var blobUrl = window.URL.createObjectURL(data);
                    var downloadLink = document.createElement('a');
                    downloadLink.href = blobUrl;
                    downloadLink.download = 'download.zip';
                    downloadLink.click();
                };
                request.send();
                return request
            }

            xhrButtonSuccess.addEventListener('click', () => {
                runXHR('https://abbbbbc.com/download.zip');
            });

            xhrButtonError.addEventListener('click', () => {
                runXHR('http://i-dont-exist');
            });

            xhrButtonAbort.addEventListener('click', () => {
                runXHR('https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json').abort();
            });
        </script>

    </body>
</html>

Return to post

참조: XMLHttpRequest: progress event, Live 예시

버튼/링크를 클릭하면 iframe을 생성하여 본문에 추가합니다.

$('<iframe />')
    .attr('src', url)
    .attr('id', 'iframe_download_report')
    .hide()
    .appendTo('body');

지연이 있는 iframe을 생성하여 다운로드 후 삭제합니다.

var triggerDelay =   100;
var cleaningDelay =  20000;
var that = this;
setTimeout(function() {
    var frame = $('<iframe style="width:1px; height:1px;" class="multi-download-frame"></iframe>');
    frame.attr('src', url + "?" + "Content-Disposition: attachment ; filename=" + that.model.get('fileName'));
    $(ev.target).after(frame);
    setTimeout(function() {
        frame.remove();
    }, cleaningDelay);
}, triggerDelay);

언급URL : https://stackoverflow.com/questions/1106377/detect-when-a-browser-receives-a-file-download

반응형