JW.*_*JW. 464 javascript mime http
我有一个页面,允许用户下载动态生成的文件.生成需要很长时间,所以我想显示一个"等待"指标.问题是,我无法弄清楚如何检测浏览器何时收到文件,所以我可以隐藏指标.
我正在以隐藏的形式发出请求,该请求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
Run Code Online (Sandbox Code Playgroud)
这适用于Firefox; 它接收空的HTML文件,触发"load"事件,然后显示可下载文件的"Save"对话框.但它在IE和Safari上失败了; IE触发"加载"事件但不下载文件,Safari下载文件(名称和内容类型错误),并且不会触发"加载"事件.
一种不同的方法可能是调用启动文件创建,然后轮询服务器直到它准备就绪,然后下载已经创建的文件.但我宁愿避免在服务器上创建临时文件.
有没有人有更好的主意?
bul*_*ous 432
一种可能的解决方案是在客户端使用JavaScript.
客户端算法:
服务器算法:
客户端源代码(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;
}
Run Code Online (Sandbox Code Playgroud)
示例服务器代码(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();
Run Code Online (Sandbox Code Playgroud)
哪里:
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
);
}
Run Code Online (Sandbox Code Playgroud)
小智 27
一个非常简单(和蹩脚)的单行解决方案是使用该window.onblur()事件来关闭加载对话框.当然,如果花费太长时间并且用户决定做其他事情(比如阅读电子邮件),则加载对话框将关闭.
byt*_*ate 13
老线程,我知道......
但那些谷歌引领的人可能会对我的解决方案感兴趣.它非常简单,但又可靠.它可以显示真实的进度消息(并且可以轻松插入现有进程):
处理的脚本(我的问题是:通过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>
Run Code Online (Sandbox Code Playgroud)
getstatus.php
<?php
session_start();
echo json_encode($_SESSION['downloadstatus']);
?>
Run Code Online (Sandbox Code Playgroud)
的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
?>
Run Code Online (Sandbox Code Playgroud)
Elm*_*mer 11
我使用以下内容下载blob并在下载后撤消object-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;
}
Run Code Online (Sandbox Code Playgroud)
关闭文件下载对话框后,窗口会将焦点恢复,以便触发焦点事件.
Cub*_*oft 11
核心问题是 Web 浏览器没有在取消页面导航时触发的事件,但确实有在页面完成加载时触发的事件。直接浏览器事件之外的任何事情都将有利有弊。
有四种已知的方法来处理检测浏览器下载何时开始:
a带有download属性的标签,并触发点击事件。现代 Web 浏览器将为用户提供保存已检索文件的选项。这种方法有几个缺点:load如果页面在 iframe 中加载而不是开始下载,则iframe 会触发事件,但如果下载开始,则不会触发任何事件。然后可以通过 Javascript 循环检测使用 Web 服务器设置的 cookie。这种方法有几个缺点:load上的iframe事件发生的HTML文档加载时。这种方法有几个缺点:loadiframe 上的事件触发,则发生错误,中止 XHR 请求,并删除 iframe。如果 XHRprogress事件触发,则下载可能已在 iframe 中开始,中止 XHR 请求,等待几秒钟,然后删除 iframe。这允许在不依赖服务器端 cookie 的情况下下载更大的文件。这种方法有几个缺点:如果没有合适的内置 Web 浏览器事件,这里没有任何完美的解决方案。但是,根据您的用例,上述四种方法中的一种可能比其他方法更适合。
只要有可能,就立即将响应流式传输到客户端,而不是先在服务器上生成所有内容,然后再发送响应。可以流式传输各种文件格式,例如 CSV、JSON、XML、ZIP等。这实际上取决于找到支持流式传输内容的库。当请求一开始就流式传输响应时,检测下载的开始并不重要,因为它几乎会立即开始。
另一种选择是预先输出下载标头,而不是等待所有内容首先生成。然后生成内容,最后开始发送给客户端。用户的内置下载器将耐心等待数据开始到达。缺点是底层网络连接可能会超时等待数据开始流动(在客户端或服务器端)。
Man*_*ota 10
来自其他地方的有效解决方案:
/**
* 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');
a.remove(); // remove element
})
.catch(() => alert('An error sorry'));
}
Run Code Online (Sandbox Code Playgroud)
你可以使用它:
downloadURI("www.linkToFile.com", "file.name");
Run Code Online (Sandbox Code Playgroud)
基于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();
}
Run Code Online (Sandbox Code Playgroud)
的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>
Run Code Online (Sandbox Code Playgroud)
现在,您应该实现任何要下载的元素:
<a class="download" href="file://www.ocelot.com.pl/prepare-report">Download report</a>
Run Code Online (Sandbox Code Playgroud)
要么
<input class="download" type="submit" value="Download" name="actionType">
Run Code Online (Sandbox Code Playgroud)
每次下载后,您都会看到报告正在创建的消息,请稍候...
小智 8
我写了一个简单的JavaScript类,它实现了一种类似于在背叛答案中描述的技术.我希望这对某人有用.GitHub项目名为response-monitor.js
默认情况下,它使用spin.js作为等待指示符,但它还提供了一组用于实现自定义指标的回调.
支持JQuery但不是必需的.
值得注意的功能
用法示例
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>
Run Code Online (Sandbox Code Playgroud)
客户端(纯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
Run Code Online (Sandbox Code Playgroud)
客户端(JQuery)
$('.my_anchors').ResponseMonitor();
$('#my_form').ResponseMonitor({timeout: 20});
Run Code Online (Sandbox Code Playgroud)
带回调的客户端(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);
Run Code Online (Sandbox Code Playgroud)
服务器(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
Run Code Online (Sandbox Code Playgroud)
有关更多示例,请查看存储库中的examples文件夹.
小智 7
“如何检测浏览器何时接收文件下载?”
我在那个配置中遇到了同样的问题:
struts 1.2.9
jquery-1.3.2。
jquery-ui-1.7.1.custom
IE 11
java 5
我的 cookie 解决方案:
- 客户端:
提交表单时,调用 javascript 函数来隐藏页面并加载等待的微调器
function loadWaitingSpinner(){
... hide your page and show your spinner ...
}
Run Code Online (Sandbox Code Playgroud)
然后,调用一个函数,每 500 毫秒检查一次 cookie 是否来自服务器。
function checkCookie(){
var verif = setInterval(isWaitingCookie,500,verif);
}
Run Code Online (Sandbox Code Playgroud)
如果找到 cookie,则每 500 毫秒停止检查一次,使 cookie 过期并调用您的函数返回您的页面并删除等待的微调器(removeWaitingSpinner())。如果您希望能够再次下载另一个文件,请务必使 cookie 过期!
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 ...
}
Run Code Online (Sandbox Code Playgroud)
- 服务器端:
在服务器进程结束时,将 cookie 添加到响应中。当您的文件可供下载时,该 cookie 将发送到客户端。
Cookie waitCookie = new Cookie("waitingCookie", "done");
response.addCookie(waitCookie);
Run Code Online (Sandbox Code Playgroud)
我希望能帮助某人!
我参加聚会已经很晚了,但如果其他人想知道我的解决方案,我会把它放在这里:
我对这个确切的问题进行了真正的斗争,但我找到了一个使用 iframes 的可行解决方案(我知道,我知道。这很糟糕,但它适用于我遇到的一个简单问题)
我有一个 html 页面,它启动了一个单独的 php 脚本来生成文件然后下载它。在 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>
Run Code Online (Sandbox Code Playgroud)
在 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*
}
Run Code Online (Sandbox Code Playgroud)
为了解决这个问题,jquery 首先在 iframe 中启动您的 php 脚本。一旦文件生成,就会加载 iframe。然后 jquery 再次使用请求变量启动脚本,告诉脚本下载文件。
无法一次性完成下载和文件生成的原因是 php header() 函数。如果您使用 header(),您将脚本更改为网页以外的内容,并且 jquery 永远不会将下载脚本识别为“已加载”。我知道这可能不一定检测浏览器何时收到文件,但您的问题听起来与我的相似。
如果您正在流式传输要动态生成的文件,并且已实现了实时的服务器到客户端消息传递库,则可以非常轻松地向客户端发出警报。
我喜欢并推荐的服务器到客户端消息传递库是Socket.io(通过Node.js)。服务器脚本完成后,生成正在传输的文件以供下载,该脚本中的最后一行可以向Socket.io发出消息,该消息将通知发送给客户端。在客户端上,Socket.io侦听服务器发出的传入消息,并允许您对其进行操作。与其他方法相比,使用此方法的好处是您可以在流完成后检测到“ true”完成事件。
例如,您可以在单击下载链接后显示忙碌指示器,流式传输文件,在流式传输脚本的最后一行中从服务器向Socket.io发送消息,在客户端上侦听通知,接收通知并通过隐藏忙碌指示器来更新您的UI。
我知道大多数阅读此问题答案的人可能没有这种类型的设置,但是我在自己的项目中使用了这种精确的解决方案来产生了很大的效果,并且效果很好。
Socket.io非常易于安装和使用。查看更多:http : //socket.io/
| 归档时间: |
|
| 查看次数: |
283721 次 |
| 最近记录: |