我有一个工作的PHP脚本运行相当长(最多5分钟)。我想在每一步之后通知用户("task 1 of 10 done")。
我知道,这是一个老问题,我试图解决它与服务器端事件。
在HTML端有一个表单,我的PHP脚本作为动作:
<form action="my_script.php"><input type="submit" value="Submit!"/></form>
一个要求是,这个HTML页面在没有任何脚本的情况下工作(并通过提交表单调用my_script.php)。PHP脚本中的"实时状态报告"本来是一个额外的功能,但是没有它,页面应该完全可以工作。
这就是我如何尝试这样做:在客户端,我添加了一个onsubmit()
事件到form
标签调用JavaScript函数:
var source = new EventSource("my_script.php");
source.onmessage = function(event)
{
// display data in a div
};
在服务器端,PHP应该向调用的HTML页面发送一些消息(同时执行其任务),并在所有任务完成后构建具有所有输出的新HTML页面。我试着这样做:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$output_stream = '';
// do something here. If a taks is done call send_to_output('something done');
function send_to_output($message)
{
global $output_stream;
echo "data: {$message}'n'n";
flush();
$output_stream .= $message;
}
header('Content-Type: text/html');
header('Cache-Control: no-cache');
echo "Everything is finished";
echo $output_stream;
但这行不通。
经过一些测试,我认为有两个问题:
- 浏览器无法处理标题'Content-Type: text/event-stream',并提供下载文件
- 表单动作(
<form action="my_script.php">
)和JavaScript事件(var source = new EventSource("my_script.php");
)似乎都打开了一个自己的PHP脚本线程,所以有两个实例运行在服务器上。
我怎样才能解决这些问题?
在过去,要实现这一点,您的表单将直接提交到my_script.php
,这将指导浏览器从该URL加载内容。my_script.php
将首先回显足够的HTML以使浏览器呈现整个页面,然后刷新其输出缓冲区以确保客户端接收到所有内容。部分页面数据将是JS全局函数——类似update_status
,它将接收状态事件并更新UI。然后,对于同一个请求,它将启动处理。
在处理过程中的不同时刻,脚本会回显如下内容:
<script type="text/javascript">update_status('15%');</script>
并将其刷新到客户端。客户端将运行这些脚本标签,调用相关函数,并更新UI。
今天我们有WebSockets。这是迄今为止最好的方法,只要你的用户有一个功能强大的浏览器。
您也可以在setInterval
上轮询服务器,但这需要将执行分散到不同的进程中(以便不挂起浏览器或浪费web服务器工作器),并在后端添加端点以公开每个进程的状态。
我将使用标准ajax轮询完成此操作。在这个例子中,我只是从长时间运行的脚本中写入一个文本文件,并直接在poll中加载它。
你可能会写一个数据库等:
<html><head></head>
<body>
<form action="long.php" method="post" id="myform">
<input type="submit" value="submit "/>
</form>
<div id="progress">
</div>
<div id="result">
</div>
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script type="application/javascript">
$(function(){
var checkid ='';
$('#myform').submit(function(ev){
ev.preventDefault();
var fm = $(this);
$.ajax({
type: "POST",
url: fm.attr('action'),
data: "",
success: function(data){
clearInterval(checkid);
$('#result').html(data);
}
});
checkid = setInterval(function(){
$.get('progress.txt', function(data){
$('#progress').html(data);
});
},1000);
})
});
</script>
</body>
</html>
//long.php
for($i=1;$i<10;$i++){
sleep(2);
file_put_contents('progress.txt', "done {$i} iterations", LOCK_EX);
}
echo 'all done';
对于这样的问题有相当多的解决方案或变通方法。这里肯定不是讨论哪一个是最好的。我的问题的意图只是:如何用服务器端事件解决这个问题?
受user574632的启发,我现在找到了一个快速而肮脏的解决方案:
我长时间运行的PHP脚本将所有消息放入一个文件:
function send_to_output($message)
{
global $output_stream;
$output_stream .= $message;
file_put_contents('progress.txt',$output_stream);
}
另一个PHP脚本将此文件的所有内容发送到客户端:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$file_content = file_get_contents('progress.txt');
echo "data: {$file_content}'n'n";
flush();
?>
在HTML中,我只需将文件名更改为var source = new EventSource("name.php")
。
这似乎工作,但它不是真正的光滑和好看的解决方案。