显示长时间运行的PHP脚本的进度
我有一个PHP可能要花费10甚至更多的时间。 我想为用户显示它的进度。
在正在执行的类中,我有一个属性$progress
更新与进展(在1-100),方法get_progress()
(这是目的应该是显而易见的)。
问题是,如何更新前端的<progress>
元素供用户查看?
我认为AJAX是解决scheme,但我无法绕开它。 (我不能到达相同的对象实例)。 提前致谢
如果你的任务是上传一个庞大的数据集或者在服务器上处理它,在更新进程到服务器的过程中,你应该考虑使用某种作业体系结构,在其中启动作业,并使用服务器(例如缩放/处理图像等)。 在这里,你一次做一件事,从而形成一个有input和最终处理结果的任务stream水线。
在stream水线的每一步,任务的状态在数据库内被更新,然后可以通过现在存在的任何服务器推送机制发送给用户。 运行处理上传和更新的单个脚本会在您的服务器上加载负载,并限制您(如果浏览器closures,发生其他错误会怎么样)。 当进程分成几个步骤时,可以从最后一个成功的地方恢复失败的任务。
有很多方法可以做到这一点。 但总体stream程看起来像这样
下面的方法是我为一个个人项目做的,这个脚本对上传和处理成千上万的高分辨率图像到我的服务器很好,然后被缩小到多个版本,上传到亚马逊S3,同时识别其中的对象。 (我原来的代码是在Python中)
步骤1 :
启动运输或任务
首先上传您的内容,然后通过简单的POST请求立即返回此交易的交易ID或uuid。 如果您在任务中执行多个文件或多个任务,则可能还需要在此步骤中处理该逻辑
第2步:
做好工作并返回进度。
一旦你已经知道如何交易发生,你可以使用任何服务器端推技术来发送更新数据包。 我会selectWebSocket或服务器发送事件,无论哪种情况都适用于不支持的浏览器上的长轮询。 一个简单的SSE方法看起来像这样。
function TrackProgress(upload_id){ var progress = document.getElementById(upload_id); var source = new EventSource('/status/task/' + upload_id ); source.onmessage = function (event) { var data = getData(event); // your custom method to get data, i am just using json here progress.setAttribute('value', data.filesDone ); progress.setAttribute('max', data.filesTotal ); progress.setAttribute('min', 0); }; } request.post("/me/photos",{ files: files }).then(function(data){ return data.upload_id; }).then(TrackProgress);
在服务器端,你将需要创build一个跟踪任务的东西,一个带有job_id和进度数据库的简单的Jobs架构就足够了。 我会把工作安排给你和路由,但是在那之后,概念代码(最简单的SSE就足够了上面的代码)如下。
<?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); /* Other code to take care of how do you find how many files are left this is really not required */ function sendStatusViaSSE($task_id){ $status = getStatus($task_id); $json_payload = array('filesDone' => $status.files_done, 'filesTotal' => $status.files_total); echo 'data: ' . json_encode( $json_payload ) . '\n\n'; ob_flush(); flush(); // End of the game if( $status.done ){ die(); } } while( True ){ sendStatusViaSSE( $request.$task_id ); sleep(4); } ?>
SSE的一个很好的教程可以在这里findhttp://html5doctor.com/server-sent-events/
你可以阅读更多关于从这个问题推送更新从服务器推送更新
以上只是一个概念性的解释,还有其他方法可以达到这个目的,但是这个解决scheme对我来说是一个相当大的任务。
我将把这里作为任何人search的参考 – 这依靠没有JavaScript的..
<?php /** * Quick and easy progress script * The script will slow iterate through an array and display progress as it goes. */ #First progress $array1 = array(2, 4, 56, 3, 3); $current = 0; foreach ($array1 as $element) { $current++; outputProgress($current, count($array1)); } echo "<br>"; #Second progress $array2 = array(2, 4, 66, 54); $current = 0; foreach ($array2 as $element) { $current++; outputProgress($current, count($array2)); } /** * Output span with progress. * * @param $current integer Current progress out of total * @param $total integer Total steps required to complete */ function outputProgress($current, $total) { echo "<span style='position: absolute;z-index:$current;background:#FFF;'>" . round($current / $total * 100) . "% </span>"; myFlush(); sleep(1); } /** * Flush output buffer */ function myFlush() { echo(str_repeat(' ', 256)); if (@ob_get_contents()) { @ob_end_flush(); } flush(); } ?>
这是一个有点困难,(FYI)PHP过程和您的AJAX请求正在由单独的线程处理,因此您不能获得$progress
值。
一个快速的解决scheme:每次更新时你都可以将进度值写入$_SESSION['some_progress']
,那么你的AJAX请求可以通过访问$_SESSION['some_progress']
来获得进度值。
你需要使用JavaScript的setInterval()
或setTimeout()
来继续调用AJAX处理程序,直到返回值为100
。
这不是完美的解决scheme,但它的快速和容易。
因为您不能同时使用同一个会话两次,请改用数据库。 将状态写入数据库,然后用间隔的AJAX调用读取。
这是一个老问题,但我也有类似的需求。 我想用php system()
命令运行脚本并显示输出。
我没有做投票。
对于第二个Rikudoit案件应该是这样的:
JavaScript的
document.getElementById("formatRaid").onclick=function(){ var xhr = new XMLHttpRequest(); xhr.addEventListener("progress", function(evt) { var lines = evt.currentTarget.response.split("\n"); if(lines.length) var progress = lines[lines.length-1]; else var progress = 0; document.getElementById("progress").innerHTML = progress; }, false); xhr.open('POST', "getProgress.php", true); xhr.send(); }
PHP
<?php header('Content-Type: application/octet-stream'); header('Cache-Control: no-cache'); // recommended to prevent caching of event data. // Turn off output buffering ini_set('output_buffering', 'off'); // Turn off PHP output compression ini_set('zlib.output_compression', false); // Implicitly flush the buffer(s) ini_set('implicit_flush', true); ob_implicit_flush(true); // Clear, and turn off output buffering while (ob_get_level() > 0) { // Get the curent level $level = ob_get_level(); // End the buffering ob_end_clean(); // If the current level has not changed, abort if (ob_get_level() == $level) break; } while($progress < 100) { // STUFF TO DO... echo '\n' . $progress; } ?>
解决scheme是:
-
Ajax轮询 – 在服务器端存储进度,然后使用ajax调用来定期获取进度。
-
服务器发送事件 – 一个html5function,允许从服务器发送输出生成dom事件。 这是这种情况的最佳解决scheme,但IE 10不支持。
-
脚本/ Iframestream式传输 – 使用iframe来stream式处理长时间运行的脚本的输出,该脚本将输出脚本标记作为可在浏览器中产生一些反应的间隔。
你有没有考虑过输出JavaScript和使用stream刷新? 它看起来像这样
echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>'; flush();
由于刷新,此输出立即发送到浏览器。 从长时间运行的脚本定期执行它,它应该工作得很漂亮。
在这里,我只是想添加2个关于@Jarod Law上面写的问题https://stackoverflow.com/a/7049321/2012407
确实非常简单而有效。 我调整和使用:)所以我的2个担心是:
-
而不是使用
setInterval()
或setTimeout()
在callback中使用recursion调用,如:function trackProgress() { $.getJSON(window.location, 'ajaxTrackProgress=1', function(response) { var progress = response.progress; $('#progress').text(progress); if (progress < 100) trackProgress();// You can add a delay here if you want it is not risky then. }); }
由于呼叫是asynchronous的,可能会以不需要的顺序返回。
-
保存到
$_SESSION['some_progress']
是聪明的,你可以,不需要数据库存储。 你真正需要的是允许同时调用这两个脚本,而不是被PHP排队。 所以你最需要的是session_write_close()
! 我在这里发布了一个非常简单的演示示例: https : //stackoverflow.com/a/38334673/2012407
在Web应用程序中,我们可能会向后端系统发出请求,这可能会触发长时间运行的进程,如search大量数据或运行时间过长的数据库。 然后,前端网页可能会挂起,等待过程完成。 在这个过程中,如果我们能够向用户提供一些关于后端进程的信息,可以改善用户体验。 不幸的是,在Web应用程序中,这似乎不是一件容易的事情,因为Web脚本语言不支持multithreading,HTTP是无状态的。 我们现在可以通过AJAX来模拟实时进程。
基本上我们需要三个文件来处理请求。 第一个是运行实际的长时间运行的脚本的脚本,它需要有一个会话variables来存储进度。 第二个脚本是状态脚本,它会在长时间运行的作业脚本中回显会话variables。 最后一个是客户端AJAX脚本,它可以频繁轮询状态脚本。
有关实现的详细信息,可以参考PHP来dynamic获取长时间运行的进程进度