PHP脚本作为表单操作:提供服务器端响应


PHP script as form action: give server side effents

我有一个工作的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;

但这行不通。

经过一些测试,我认为有两个问题:

  1. 浏览器无法处理标题'Content-Type: text/event-stream',并提供下载文件
  2. 表单动作(<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")

这似乎工作,但它不是真正的光滑和好看的解决方案。