【问题标题】:PHP script as form action: give server side effentsPHP 脚本作为表单操作:提供服务器副作用
【发布时间】:2014-09-23 10:37:59
【问题描述】:

我有一个运行很长时间的 PHP 脚本(最多 5 分钟)。我想在每一步之后通知用户(“完成 10 项任务中的第 1 项”)。

我知道,这是一个老问题,我尝试通过服务器端事件来解决它。

在 HTML 端有一个表单,其中包含我的 PHP 脚本作为操作:

<form action="my_script.php"><input type="submit" value="Submit!"/></form>

一个要求是,hole HTML 页面可以在没有任何脚本的情况下工作(并通过提交表单调用 my_script.php)。 PHP 脚本中的“实时状态报告”旨在成为一项附加功能,但如果没有它,页面应该可以完全正常工作。

这就是我尝试这样做的方式:在客户端,我向调用 JavaScript 函数的 form 标记添加了一个 onsubmit() 事件:

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. 表单操作 (&lt;form action="my_script.php"&gt;) 和 JavaScript 事件 (var source = new EventSource("my_script.php");) 似乎都打开了自己的 PHP 脚本线程,因此该脚本有 两个 实例运行服务器。

我该如何解决这些问题?

【问题讨论】:

  • 如果这只是一个常规的表单提交,它在处理过程中不会在浏览器中停止执行吗?
  • 为什么要发明自己的自行车?我想要类似 sockets.io 的东西
  • 请确认如果客户端不支持javascript会发生什么
  • @adeneo:我不明白你的意思。我的问题是:脚本执行良好,但仅在完全完成后才打印文本。我希望用户在脚本仍在运行时看到消息。
  • @user574632:如果客户端不支持 JavaScript,用户单击“提交”,PHP 脚本会运行并在几分钟后显示其结果。不显示任何状态消息。这已经奏效了。所以,这里没问题。

标签: javascript php html server-sent-events


【解决方案1】:

对于此类问题,有很多解决方案或变通方法。这肯定不是讨论哪个一般最好的地方。我的问题的意图只是:如何用服务器端事件解决这个问题?

受 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")

这似乎可行,但它不是真正流畅且美观的解决方案。

【讨论】:

    【解决方案2】:

    我会使用标准的 ajax 轮询来做到这一点。 在这个例子中,我只是从长时间运行的脚本中写入一个文本文件,然后直接在投票中加载它。

    您可能会写入数据库等:

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

    【讨论】:

      【解决方案3】:

      在过去,为了实现这一点,您的表单将直接提交到my_script.php,这将指导浏览器从该 URL 加载内容。 my_script.php 将首先回显足够的 HTML 以供浏览器呈现整个页面,然后刷新其输出缓冲区以确保客户端接收到所有内容。 该页面数据的一部分将是一个 JS 全局函数——类似于update_status,它将接收状态事件并更新 UI。然后,在同一个请求上,它会开始处理。

      在处理过程中的不同点,脚本会回显如下内容:

      <script type="text/javascript">update_status('15%');</script>
      

      并将其刷新到客户端。客户端会在这些脚本标签进来时运行它们,调用相关函数,并更新 UI。

      今天我们有WebSockets。这是迄今为止最好的方法,前提是您的用户拥有功能强大的浏览器。

      您也可以在 setInterval 上轮询服务器,但这需要将执行拆分到不同的进程中(以免挂起浏览器或浪费 Web 服务器工作人员)并向后端添加一个端点以公开每个进程的状态。

      【讨论】:

      • 啊,好的。我查看了dsheiko.com/weblog/websockets-vs-sse-vs-long-polling 并得出结论,服务器端事件是解决我的问题的更好解决方案,因为我认为 WebSockets 对于这样一个小问题来说过于复杂了。但会调查它。
      • WebSocket 有点宽泛,我同意,但根据caniuse.com 的说法,它们比 EventSource 有更好的支持——尤其是在 IE 中。 EventSource 的一个问题是,如果您使用像 Apache 这样的基于进程的服务器,它会吃掉 Web 服务器工作人员。即使在大型服务器上,打开数百个这样的连接也会使您的网站无响应。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-13
      • 2020-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多