ตรวจหาเมื่อเบราว์เซอร์ได้รับการดาวน์โหลดไฟล์


488

ฉันมีหน้าเว็บที่อนุญาตให้ผู้ใช้ดาวน์โหลดไฟล์ที่สร้างขึ้นแบบไดนามิก ใช้เวลานานในการสร้างดังนั้นฉันต้องการแสดงตัวบ่งชี้ "กำลังรอ" ปัญหาคือฉันไม่สามารถหาวิธีตรวจสอบเมื่อเบราว์เซอร์ได้รับไฟล์ดังนั้นฉันสามารถซ่อนตัวบ่งชี้

ฉันกำลังร้องขอในรูปแบบที่ซ่อนอยู่ซึ่งโพสต์ไปยังเซิร์ฟเวอร์และกำหนดเป้าหมาย iframe ที่ซ่อนอยู่สำหรับผลลัพธ์ นี่คือสิ่งที่ฉันไม่ได้แทนที่หน้าต่างเบราว์เซอร์ทั้งหมดด้วยผลลัพธ์ ฉันฟังเหตุการณ์ "โหลด" ใน iframe ด้วยความหวังว่ามันจะทำงานเมื่อการดาวน์โหลดเสร็จสิ้น

ฉันส่งคืนหัวข้อ "เนื้อหาการจัดการ: ไฟล์แนบ" พร้อมไฟล์ซึ่งทำให้เบราว์เซอร์แสดงข้อความโต้ตอบ "บันทึก" แต่เบราว์เซอร์ไม่ได้เรียกใช้เหตุการณ์ "โหลด" ใน 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 จะดาวน์โหลดไฟล์ (ที่มีชื่อและประเภทเนื้อหาผิด) และจะไม่เริ่มการทำงานของเหตุการณ์ "โหลด"

วิธีการอื่นอาจเป็นการโทรเพื่อเริ่มการสร้างไฟล์จากนั้นสำรวจเซิร์ฟเวอร์จนกว่าจะพร้อมแล้วดาวน์โหลดไฟล์ที่สร้างขึ้นแล้ว แต่ฉันควรหลีกเลี่ยงการสร้างไฟล์ชั่วคราวบนเซิร์ฟเวอร์

ใครบ้างมีความคิดที่ดีกว่า


4
ไม่มีเวอร์ชั่นของ IE ที่รองรับ multipart / x-Mixed-replace
EricLaw

ขอบคุณ Eric - ดีที่รู้ ฉันจะไม่เสียเวลาอีกต่อไปกับแนวทางนั้น
เจดับบลิว

ดูเหมือนว่าวิธีที่เชื่อถือได้เท่านั้นคือการแจ้งเตือนแบบพุชเซิร์ฟเวอร์ (SignalR for ASP.NET folks)
dudeNumber4

1
bennadel.com/blog/… - นี่เป็นวิธีง่ายๆ
Mateen

1
@ เพื่อนร่วมงานขอบคุณเพื่อน! มันง่ายจริงๆ
Fai Zal Dong

คำตอบ:


451

ทางออกหนึ่งที่เป็นไปได้ใช้ JavaScript กับลูกค้า

อัลกอริทึมไคลเอนต์:

  1. สร้างโทเค็นที่ไม่ซ้ำแบบสุ่ม
  2. ส่งคำขอดาวน์โหลดและรวมโทเค็นในฟิลด์ GET / POST
  3. แสดงสัญลักษณ์ "กำลังรอ"
  4. เริ่มจับเวลาและทุก ๆ วินาทีหรือมองหาคุกกี้ชื่อ "fileDownloadToken" (หรือสิ่งที่คุณตัดสินใจ)
  5. หากมีคุกกี้อยู่และค่านั้นตรงกับโทเค็นให้ซ่อนตัวบ่งชี้ "กำลังรอ"

อัลกอริทึมเซิร์ฟเวอร์:

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

4
ความคิดที่ยอดเยี่ยมฉันใช้มันเป็นกรอบพื้นฐานสำหรับคำตอบนี้เกี่ยวกับการดาวน์โหลดไฟล์หลาย ๆ ไฟล์ด้วย jQuery / C #
Greg

7
ข้อแนะนำสำหรับผู้อื่น: หาก document.cookies ไม่รวม downloadToken ให้ตรวจสอบเส้นทางคุกกี้ ในกรณีของฉันฉันต้องตั้งค่าพา ธ เป็น "/" ที่ฝั่งเซิร์ฟเวอร์ (เช่น cookie.setPath ("/") ใน Java) แม้ว่าพา ธ จะว่างเปล่าตามค่าเริ่มต้น บางครั้งฉันคิดว่าปัญหาคือการจัดการคุกกี้โดเมน 'localhost' พิเศษ ( stackoverflow.com/questions/1134290/… ) แต่นั่นไม่ใช่ปัญหาในท้ายที่สุด อาจเป็นได้ว่าสำหรับคนอื่น ๆ แต่ควรค่าแก่การอ่าน
jlpp

2
@bulltorious ก่อนที่จะเจาะลึกกับโซลูชันของคุณฉันสงสัยว่ามันจะทำงานกับคำขอดาวน์โหลดไฟล์ข้ามโดเมนหรือไม่ คุณคิดว่ามันจะหรือข้อ จำกัด คุกกี้จะประนีประนอมหรือไม่
kiks73

5
ยอดเยี่ยม - มันจะไม่เกิดขึ้นกับฉันใน 100 ปีที่คุณสามารถรวมคุกกี้เป็นส่วนหนึ่งของการดาวน์โหลดไฟล์ ขอบคุณ!!
freefaller

8
ตามที่คนอื่น ๆ ได้ชี้ให้เห็นทางออกนี้แก้ปัญหาได้เพียงบางส่วนเท่านั้นการรอให้เซิร์ฟเวอร์เตรียมเวลาไฟล์ อีกส่วนหนึ่งของปัญหาซึ่งอาจมีขนาดใหญ่ขึ้นอยู่กับขนาดของไฟล์และความเร็วในการเชื่อมต่อคือระยะเวลาในการรับไฟล์ทั้งหมดบนไคลเอนต์ และนั่นไม่ได้ถูกแก้ไขด้วยวิธีนี้
AsGoodAsItGets

27

โซลูชันหนึ่งบรรทัดที่ง่ายมาก (และอ่อนแอ) คือการใช้window.onblur()เหตุการณ์เพื่อปิดกล่องโต้ตอบการโหลด แน่นอนถ้าใช้เวลานานเกินไปและผู้ใช้ตัดสินใจทำอย่างอื่น (เช่นการอ่านอีเมล) กล่องโต้ตอบการโหลดจะปิดลง


นี่เป็นวิธีการง่าย ๆ ที่เหมาะสำหรับการกำจัดการวางซ้อนการโหลดสำหรับการดาวน์โหลดไฟล์ที่ถูกเรียกใช้onbeforeunloadขอบคุณ
wf4

5
สิ่งนี้ไม่สามารถใช้ได้กับทุกเบราว์เซอร์ (บางตัวไม่ปล่อย / เบลอหน้าต่างปัจจุบันซึ่งเป็นส่วนหนึ่งของขั้นตอนการดาวน์โหลดเช่น Safari รุ่น IE บางรุ่น ฯลฯ )
hiattp

4
Chrome และเบราว์เซอร์อื่น ๆ เช่นดาวน์โหลดไฟล์อัตโนมัติซึ่งเงื่อนไขนี้จะล้มเหลว
Lucky

@ Lucky ที่เป็นค่าเริ่มต้นเท่านั้น เป็นไปได้ทั้งหมดที่ผู้ใช้ Chrome จะระบุตำแหน่งที่จะบันทึกการดาวน์โหลดและด้วยเหตุนี้จะเห็นกล่องโต้ตอบ
ESR

2
ความคิดที่ไม่ดีเพราะคุณเปิดใช้งานความพร่ามัวบน Tabchange หรือการกระทำใด ๆ นอกหน้าต่าง
Michael

14

ด้ายเก่าฉันรู้ ...

แต่สิ่งเหล่านั้นที่นำโดย 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
    ?>

3
แต่ถ้าผู้ใช้มีหลาย windows หรือดาวน์โหลดเปิดอยู่ นอกจากนี้คุณจะได้รับสายที่ซ้ำซ้อนไปยังเซิร์ฟเวอร์ที่นี่
Yuki

3
หากคุณมีการเชื่อมต่อหลายรายการจากผู้ใช้หนึ่งรายพวกเขาจะรอให้การเชื่อมต่ออื่นสิ้นสุดลงเนื่องจาก session_start () ล็อคเซสชันสำหรับผู้ใช้และป้องกันไม่ให้กระบวนการอื่น ๆ ทั้งหมดเข้าถึง
Honza Kuchař

2
คุณไม่จำเป็นต้องใช้.each()สำหรับการลงทะเบียนกิจกรรม แค่บอกว่า$('a.download').click()
robisrob

setTimeout('getstatus()', 1000);ทำภายในรหัสไม่ EVAL ใช้ fn โดยตรง:setTimeout(getstatus, 1000);
Roko C. Buljan

11

ฉันใช้สิ่งต่อไปนี้เพื่อดาวน์โหลด blobs และยกเลิก object-url หลังจากการดาวน์โหลด มันทำงานได้ในโครเมี่ยมและ 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;
}

หลังจากปิดกล่องโต้ตอบดาวน์โหลดไฟล์หน้าต่างจะได้รับการโฟกัสกลับเพื่อให้เหตุการณ์การโฟกัสทำงาน


ยังคงมีปัญหาในการสลับหน้าต่างและกลับมาซึ่งจะทำให้โมดอลซ่อน
dudeNumber4

9
เบราว์เซอร์เช่น Chrome ที่ดาวน์โหลดลงในถาดด้านล่างจะไม่เบลอ / ปรับโฟกัสหน้าต่าง
Coleman

10

จากตัวอย่างของ Elmer ฉันได้เตรียมสารละลายของตัวเองแล้ว หลังจากองค์ประกอบคลิกด้วยคลาสดาวน์โหลดที่กำหนดจะช่วยให้สามารถแสดงข้อความที่กำหนดเองบนหน้าจอ ฉันใช้ทริกเกอร์โฟกัสเพื่อซ่อนข้อความ

JavaScript

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

หลังจากคลิกดาวน์โหลดแต่ละครั้งคุณจะเห็นข้อความรายงานของคุณกำลังสร้างโปรดรอ ...


2
เกิดอะไรขึ้นถ้าผู้ใช้คลิกหน้าต่าง?
Tom Roggero

นี่คือสิ่งที่ฉันต้องการอย่างแท้จริง
Sergio

Hide () ไม่ได้รับการโทรในกรณีของฉัน
Prashant Pimpale

8

ผมเขียนที่เรียบง่ายคลาส JavaScript ที่ดำเนินการเทคนิคที่คล้ายกับที่อธิบายไว้ใน bulltorious คำตอบ ฉันหวังว่ามันจะมีประโยชน์กับคนที่นี่ โครงการ 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

สำหรับตัวอย่างเพิ่มเติมให้ตรวจสอบโฟลเดอร์ตัวอย่างบนที่เก็บ


5

ฉันไปงานปาร์ตี้ช้ามาก แต่ฉันจะเอามาไว้ที่นี่ถ้าใครอยากรู้วิธีแก้ปัญหาของฉัน:

ฉันต่อสู้อย่างแท้จริงกับปัญหาที่แน่นอนนี้ แต่ฉันพบวิธีแก้ปัญหาที่ใช้งานได้โดยใช้ iframes (ฉันรู้ฉันรู้มันแย่มาก แต่ใช้งานได้สำหรับปัญหาง่ายๆที่ฉันมี)

ฉันมีหน้า html ที่เปิดตัว php script แยกต่างหากที่สร้างไฟล์แล้วดาวน์โหลด ในหน้า html ฉันใช้ jquery ต่อไปนี้ในส่วนหัว html (คุณจะต้องรวม jquery library ด้วย):

<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 จะเปิดใช้งานสคริปต์ php ของคุณก่อนใน iframe iframe ถูกโหลดเมื่อไฟล์ถูกสร้างขึ้น จากนั้น jquery จะเรียกใช้สคริปต์อีกครั้งพร้อมตัวแปรคำขอเพื่อแจ้งให้สคริปต์ดาวน์โหลดไฟล์

เหตุผลที่คุณไม่สามารถทำการดาวน์โหลดและสร้างไฟล์ได้ในครั้งเดียวนั้นเป็นเพราะฟังก์ชั่นส่วนหัวของ php () หากคุณใช้ header () แสดงว่าคุณกำลังเปลี่ยนสคริปต์เป็นสิ่งอื่นนอกเหนือจากเว็บเพจและ jquery จะไม่รู้จักสคริปต์ดาวน์โหลดว่า 'โหลด' ฉันรู้ว่านี่อาจไม่ถูกตรวจพบเมื่อเบราว์เซอร์ได้รับไฟล์ แต่ปัญหาของคุณฟังคล้ายกับของฉัน


5

หากคุณกำลังสตรีมไฟล์ที่คุณกำลังสร้างแบบไดนามิกและยังมีไลบรารี่การส่งข้อความจากเซิร์ฟเวอร์ถึงไคลเอ็นต์แบบเรียลไทม์คุณสามารถแจ้งเตือนลูกค้าของคุณได้อย่างง่ายดาย

ไลบรารีการส่งข้อความจากเซิร์ฟเวอร์ถึงไคลเอนต์ที่ฉันชอบและแนะนำคือ Socket.io (ผ่าน Node.js) หลังจากสคริปต์เซิร์ฟเวอร์ของคุณเสร็จสิ้นการสร้างไฟล์ที่กำลังสตรีมสำหรับดาวน์โหลดบรรทัดสุดท้ายของคุณในสคริปต์นั้นสามารถส่งข้อความไปยัง Socket.io ซึ่งจะส่งการแจ้งเตือนไปยังลูกค้า บนไคลเอนต์ Socket.io ฟังข้อความขาเข้าที่ปล่อยออกมาจากเซิร์ฟเวอร์และช่วยให้คุณสามารถดำเนินการกับพวกเขา ประโยชน์ของการใช้วิธีนี้เหนือผู้อื่นคือคุณสามารถตรวจจับเหตุการณ์เสร็จสิ้น "ของจริง" หลังจากการสตรีมเสร็จสิ้น

ตัวอย่างเช่นคุณสามารถแสดงตัวบ่งชี้ไม่ว่างหลังจากคลิกลิงค์ดาวน์โหลดสตรีมไฟล์ของคุณส่งข้อความไปยัง Socket.io จากเซิร์ฟเวอร์ในบรรทัดสุดท้ายของสคริปต์สตรีมมิ่งของคุณฟังไคลเอนต์สำหรับการแจ้งเตือนรับการแจ้งเตือน และอัปเดต UI ของคุณโดยการซ่อนตัวบ่งชี้ที่ไม่ว่าง

ฉันรู้ว่าคนส่วนใหญ่ที่อ่านคำตอบสำหรับคำถามนี้อาจไม่มีการตั้งค่าประเภทนี้ แต่ฉันใช้วิธีนี้เพื่อผลที่ยอดเยี่ยมในโครงการของฉันเองและมันทำงานได้อย่างยอดเยี่ยม

Socket.io ติดตั้งและใช้งานง่ายอย่างไม่น่าเชื่อ ดูเพิ่มเติม: http://socket.io/


5

"วิธีตรวจสอบเมื่อเบราว์เซอร์ได้รับการดาวน์โหลดไฟล์"
ฉันประสบปัญหาเดียวกันกับ config:
struts 1.2.9
jquery-1.3.2
jquery-ui-1.7.1.custom
IE 11
java 5


โซลูชันของฉันพร้อมคุกกี้:
- ฝั่งไคลเอ็นต์:
เมื่อส่งแบบฟอร์มของคุณให้เรียกใช้ฟังก์ชันจาวาสคริปต์เพื่อซ่อนหน้าของคุณและโหลดเครื่องปั่นด้ายรอ

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

จากนั้นเรียกใช้ฟังก์ชันที่จะตรวจสอบทุก ๆ 500ms ว่ามีคุกกี้มาจากเซิร์ฟเวอร์หรือไม่

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

หากพบคุกกี้ให้หยุดตรวจสอบทุก ๆ500 มิลลิวินาทีหมดอายุคุกกี้และเรียกใช้ฟังก์ชันของคุณเพื่อกลับมาที่หน้าของคุณและลบสปินเนอร์ที่รออยู่ ( 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);

ฉันหวังว่าจะช่วยใครบางคน!


มันทำงานได้อย่างสมบูรณ์แบบ ขอบคุณสำหรับตัวอย่างที่สวยงามนี้
Sedat Kumcu

4

เมื่อผู้ใช้ทริกเกอร์การสร้างไฟล์คุณสามารถกำหนด ID เฉพาะให้กับ "ดาวน์โหลด" นั้นและส่งผู้ใช้ไปยังเพจที่รีเฟรช (หรือตรวจสอบกับ AJAX) ทุกสองสามวินาที เมื่อไฟล์เสร็จสิ้นให้บันทึกภายใต้ ID ที่ไม่ซ้ำกันและ ...

  • หากไฟล์พร้อมให้ทำการดาวน์โหลด
  • หากไฟล์ไม่พร้อมแสดงความคืบหน้า

จากนั้นคุณสามารถข้ามทั้ง iframe / wait / browserwindow ระเบียบได้ แต่ก็มีทางออกที่ยอดเยี่ยมจริงๆ


ฟังดูเหมือนวิธีเข้าแฟ้มชั่วคราวที่ฉันพูดถึงข้างต้น ฉันอาจทำอะไรแบบนี้ถ้ามันเป็นไปไม่ได้ แต่ฉันหวังว่าจะหลีกเลี่ยง
เจดับบลิว

3

หากคุณไม่ต้องการสร้างและจัดเก็บไฟล์บนเซิร์ฟเวอร์คุณยินดีที่จะเก็บสถานะเช่นความคืบหน้าของไฟล์เสร็จสมบูรณ์หรือไม่ หน้า "กำลังรอ" ของคุณสามารถสำรวจเซิร์ฟเวอร์เพื่อให้ทราบว่าการสร้างไฟล์เสร็จสมบูรณ์เมื่อใด คุณจะไม่ทราบแน่ชัดว่าเบราว์เซอร์เริ่มดาวน์โหลด แต่คุณมั่นใจ


2

ฉันเพิ่งมีปัญหาเดียวกันนี้ ทางออกของฉันคือการใช้ไฟล์ชั่วคราวตั้งแต่ฉันสร้างไฟล์ชั่วคราวจำนวนมากแล้ว แบบฟอร์มจะถูกส่งด้วย:

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 ถูกเริ่มทำงาน จากนั้นข้อความรอจะปิดและการดาวน์โหลดไฟล์จะเริ่มขึ้น ทดสอบบน IE7 และ Firefox


2

จากประสบการณ์ของฉันมีสองวิธีในการจัดการสิ่งนี้:

  1. ตั้งค่าคุกกี้อายุสั้น ๆ ในการดาวน์โหลดและให้ JavaScript ตรวจสอบการมีอยู่อย่างต่อเนื่อง มีเพียงปัญหาจริงเท่านั้นที่ทำให้อายุการใช้งานของคุกกี้สั้นลงและสั้นเกินไปและ JS อาจพลาดได้นานเกินไปและอาจยกเลิกหน้าจอดาวน์โหลดสำหรับการดาวน์โหลดอื่น ๆ การใช้ JS เพื่อลบคุกกี้เมื่อค้นพบมักแก้ไขปัญหานี้
  2. ดาวน์โหลดไฟล์โดยใช้ fetch / XHR คุณไม่เพียงรู้ว่าเมื่อการดาวน์โหลดไฟล์เสร็จสิ้นหากคุณใช้ XHR คุณสามารถใช้เหตุการณ์ความคืบหน้าเพื่อแสดงแถบความคืบหน้า! บันทึก blob ที่ได้ด้วยmsSaveBlobใน IE / Edge และลิงค์ดาวน์โหลด ( เช่นนี้ ) ใน Firefox / Chrome ปัญหาเกี่ยวกับวิธีนี้คือ iOS Safari ดูเหมือนจะไม่จัดการดาวน์โหลด blobs อย่างถูกต้องคุณสามารถแปลง blob ให้เป็น URL ข้อมูลด้วย FileReader และเปิดไฟล์นั้นในหน้าต่างใหม่ แต่เป็นการเปิดไฟล์ไม่ใช่การบันทึก

2

สวัสดีฉันรู้ว่าหัวข้อเก่า แต่ฉันออกจากโซลูชันที่ฉันเห็นที่อื่นและใช้งานได้:

/**
 *  download file, show modal
 *
 * @param uri link
 * @param name file name
 */
function downloadURI(uri, name) {
// <------------------------------------------       Do someting (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");

1

หากคุณมีการดาวน์โหลดไฟล์ซึ่งถูกบันทึกไว้ซึ่งต่างจากการอยู่ในเอกสารคุณจะไม่สามารถระบุได้ว่าเมื่อการดาวน์โหลดเสร็จสมบูรณ์เนื่องจากไม่ได้อยู่ในขอบเขตของเอกสารปัจจุบัน แต่เป็นกระบวนการแยกในเบราว์เซอร์


8
ฉันควรชี้แจง - ฉัน "ไม่กังวลเกินไปเมื่อการดาวน์โหลดเสร็จสมบูรณ์ถ้าฉันสามารถระบุได้ว่าเมื่อการดาวน์โหลดเริ่มต้นขึ้นนั่นก็เพียงพอแล้ว
JW

0

คำถามคือมีตัวบ่งชี้ 'กำลังรอ' ในขณะที่ไฟล์ถูกสร้างขึ้นแล้วกลับสู่ปกติเมื่อไฟล์กำลังดาวน์โหลด วิธีที่ฉันชอบสิ่งนี้คือการใช้ iFrame ที่ซ่อนอยู่และขอเหตุการณ์ onload ของเฟรมเพื่อแจ้งให้หน้าของฉันทราบเมื่อเริ่มการดาวน์โหลด แต่โหลดไม่ทำงานใน IE สำหรับการดาวน์โหลดไฟล์ (เช่นเดียวกับโทเค็นส่วนหัวของสิ่งที่แนบมา) การสำรวจเซิร์ฟเวอร์ทำงานได้ แต่ฉันไม่ชอบความซับซ้อนเพิ่มเติม ดังนั้นนี่คือสิ่งที่ฉันทำ:

  • กำหนดเป้าหมาย iFrame ที่ซ่อนไว้ตามปกติ
  • สร้างเนื้อหา แคชด้วยการหมดเวลาที่แน่นอนใน 2 นาที
  • ส่งการเปลี่ยนเส้นทาง javascript กลับไปที่ไคลเอ็นต์การโทรโดยเรียกหน้าตัวสร้างเป็นครั้งที่สอง หมายเหตุ: สิ่งนี้จะทำให้เหตุการณ์ onload เริ่มทำงานใน IE เพราะมันทำหน้าที่เหมือนหน้าปกติ
  • ลบเนื้อหาออกจากแคชและส่งไปยังลูกค้า

ข้อจำกัดความรับผิดชอบอย่าทำเช่นนี้ในเว็บไซต์ที่มีงานยุ่งเนื่องจากการแคชสามารถเพิ่มขึ้นได้ แต่จริงๆแล้วถ้าเว็บไซต์ของคุณไม่ว่างกระบวนการที่ใช้เวลานานจะทำให้คุณอดอยากไม่ได้

นี่คือสิ่งที่ codebehind ดูเหมือนซึ่งเป็นสิ่งที่คุณต้องการจริงๆ

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

0

วิธีแก้ปัญหาอย่างรวดเร็วหากคุณต้องการแสดงข้อความหรือตัวโหลด gif จนกว่าไดอะล็อกดาวน์โหลดจะปรากฏขึ้นเพื่อใส่ข้อความลงในคอนเทนเนอร์ที่ซ่อนอยู่และเมื่อคุณคลิกที่ปุ่มที่สร้างไฟล์ที่จะดาวน์โหลดคุณจะเห็นคอนเทนเนอร์ จากนั้นใช้ jquery หรือ javascript เพื่อจับโฟกัสเหตุการณ์ของปุ่มเพื่อซ่อนคอนเทนเนอร์ที่มีข้อความ


0

หาก Xmlhttprequest ด้วย blob ไม่ใช่ตัวเลือกคุณสามารถเปิดไฟล์ของคุณในหน้าต่างใหม่และตรวจสอบว่าองค์ประกอบ eny ได้รับการเติมในเนื้อหาของหน้าต่างนั้นด้วยช่วงเวลาหรือไม่

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 error page configured your application for failed requests, check for those dom elemets 
	}
}, 1000)
//Better if you specify maximun no of intervals


0

ตัวอย่าง Java / Spring นี้ตรวจพบจุดสิ้นสุดของการดาวน์โหลดซึ่งจะซ่อนตัวบ่งชี้ "กำลังโหลด ... "

วิธีการ:ด้าน JS ตั้งคุกกี้กับแม็กซ์หมดอายุอายุ 2 นาทีและสำรวจทุกวินาทีสำหรับคุกกี้หมดอายุ จากนั้นฝั่งเซิร์ฟเวอร์จะแทนที่คุกกี้นี้ด้วยเวอร์ชันก่อนหน้าอายุที่หมดอายุกระบวนการเซิร์ฟเวอร์ที่สมบูรณ์ ทันทีที่ตรวจพบการหมดอายุของคุกกี้ในการลงคะแนน JS "กำลังโหลด ... " จะถูกซ่อน

ด้าน JS

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 JS 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 JS timer detects the expired cookie earlier than 2 min. 
// (coming from the server), or the initial JS-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 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 timer to check for 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 JS 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 JS-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 sec. for any flushing concerns
        myCookie.setPath("/");
        response.addCookie(myCookie);
        //... -- presumably the download is writing to the Output Stream...
        return null;
}

คุกกี้ถูกสร้างขึ้นโดยสคริปต์ JS แต่ไม่ได้รับการปรับปรุงโดยผู้ควบคุม แต่จะรักษาค่าดั้งเดิม (0) ฉันจะอัปเดตค่าคุกกี้ได้อย่างไรโดยไม่ต้องรีเฟรชหน้าเว็บ
Shessuky

แปลกมากคุณแน่ใจได้มั้ยว่าชื่อถูกต้องตรงไหน? มันจะเขียนทับคุกกี้หากชื่อตรงกัน แจ้งให้เราทราบ
ยีน b

ค่าดั้งเดิมไม่ใช่ 0 ค่าดั้งเดิมที่ตั้งค่าใน JS คือ 2 นาที ค่าใหม่ที่เซิร์ฟเวอร์ควรจะแก้ไขด้วยคือ 0
ยีน b

คุณกำลังทำสิ่งนี้: myCookie.setPath("/"); response.addCookie(myCookie);
ยีน b

ฉันคิดออก (ด้วยเหตุผลบางอย่าง) ว่าฉันควรเพิ่มคุกกี้ก่อนทำการตอบกลับ getOutputStream (); (รับกระแสเอาต์พุตการตอบกลับเพื่อต่อท้ายไฟล์ดาวน์โหลด) มันไม่ได้นำมาพิจารณาเมื่อฉันทำมันหลังจากขั้นตอนนั้น
Shessuky

0

Primefaces ใช้การสำรวจคุกกี้เช่นกัน

https://github.com/primefaces/primefaces/blob/32bb00299d00e50b2cba430638468a4145f4edb0/src/main/resources/META-INF/resources/primefaces/core/core.js#L458

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

-2

สร้าง 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);

นี่เป็นข้อมูลที่ขาดหายไปและไม่ได้แก้ปัญหา "เมื่อใดจะซ่อนการโหลด"
Tom Roggero
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.