显示长时间运行的 PHP 脚本的进度
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7049303/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Show progress for long running PHP script
提问by Madara's Ghost
I have a PHP script that is likely to take at least 10 seconds to run. I would like to display progress for it for the user.
我有一个可能需要至少 10 秒才能运行的 PHP 脚本。我想为用户显示它的进度。
In the executing class, I have a property $progress
which is updated with the progress (in 1-100), and a method get_progress()
(which's purpose should be obvious).
在执行类中,我有一个$progress
随进度更新的属性(在 1-100 中)和一个方法get_progress()
(其目的应该很明显)。
The question is, how to update the <progress>
element on the front end for the user to see?
问题是,如何更新<progress>
前端的元素让用户看到?
I think AJAX is the solution, but I just can not get my head around it. I can not get to the same object instance.
我认为 AJAX 是解决方案,但我无法理解它。我无法访问同一个对象实例。
采纳答案by ShrekOverflow
If your task is to upload a huge data-set or process it on the server, while updating progress to the server you should consider going with some sort of jobs architecture, where you initiate the job and do it with some other script running on the server (for example scaling / processing images etc). In this you do one thing at a time, thus forming a pipeline of tasks where there is an input and a final processed output.
如果你的任务是上传一个巨大的数据集或在服务器上处理它,同时更新服务器的进度,你应该考虑使用某种作业架构,在那里你启动作业并使用运行在服务器上的其他脚本来完成服务器(例如缩放/处理图像等)。在这种情况下,您一次只做一件事,从而形成一个任务管道,其中有一个输入和一个最终处理过的输出。
At each step of pipeline the status of task is updated inside the database which can then be sent to the user by any server-push mechanism which exists these days. Running a single script which handles uploads and updates puts load on your server and also restricts you (what if the browser closes, what if some other error occurs). When process is divided into steps you can resume a failed task from the point where it succeeded last.
在管道的每一步,任务的状态都会在数据库内更新,然后可以通过当今存在的任何服务器推送机制将其发送给用户。运行处理上传和更新的单个脚本会给您的服务器带来负载并限制您(如果浏览器关闭怎么办,如果发生其他错误怎么办)。当流程被分成几个步骤时,您可以从上次成功的点恢复失败的任务。
There exists many ways to do it. But the overall process flow looks like this
有很多方法可以做到这一点。但是整个流程看起来是这样的
The following method is what I did for a personal project and this script held good for uploading and processing thousands of high resolution image to my server which then were scaled down into multiple versions and uploaded to amazon s3 while recognizing objects inside them. (My original code was in python)
以下方法是我为个人项目所做的,该脚本适用于将数千张高分辨率图像上传和处理到我的服务器,然后将其缩小为多个版本并上传到 amazon s3,同时识别其中的对象。(我的原始代码是在 python 中)
Step 1 :
第1步 :
Initiate the transport or task
启动传输或任务
First Upload your content and then return a transaction id or uuid for this transaction immediately via a simple POST request. If you are doing multiple files or multiple things in the task, you may also want to handle that logic in this step
首先上传您的内容,然后通过简单的 POST 请求立即返回此交易的交易 ID 或 uuid。如果您在任务中执行多个文件或多项操作,您可能还希望在此步骤中处理该逻辑
Step 2:
第2步:
Do the job & Return the progress.
完成工作并返回进度。
Once you have figured out how transactions occur you can then use any server side push technology to send update packet. I would choose WebSocket or Server Sent Events whichever applicable falling back to Long Polling on un-supported browsers. A simple SSE method would look like this.
一旦您弄清楚事务是如何发生的,您就可以使用任何服务器端推送技术来发送更新数据包。我会选择 WebSocket 或服务器发送的事件,无论哪种适用,在不受支持的浏览器上回退到长轮询。一个简单的 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);
On the server side, you will need to create something which keeps a track of the tasks, a simple Jobs architecture with job_id and progress sent to the db shall suffice. I would leave the job scheduling to you and the routing as well, but after that the conceptual code (for simplest SSE which will suffice the above code) is as follows.
在服务器端,你需要创建一些东西来跟踪任务,一个简单的 Jobs 架构,带有 job_id 和发送到数据库的进度就足够了。我会将作业调度留给您和路由,但之后的概念代码(对于最简单的 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);
}
?>
A good tutorial on SSE can be found here http://html5doctor.com/server-sent-events/
关于 SSE 的一个很好的教程可以在这里找到http://html5doctor.com/server-sent-events/
and you can read more about pushing updates from the server on this question Pushing updates from server
您可以在此问题上阅读有关从服务器推送更新的更多信息Pushing updates from server
The above was a conceptual explanation, there are other ways to achieve this but this was the solution that took care of a fairly huge task in my case.
以上是一个概念上的解释,还有其他方法可以实现这一点,但在我的情况下,这是解决相当大的任务的解决方案。
回答by l0ft13
I'll put this here as a reference for anyone searching - this relies on no javascript at all..
我会把它放在这里作为任何搜索者的参考 - 这根本不依赖于 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();
}
?>
回答by Jarod DY Law
It's kinda difficult, (FYI) PHP process and your AJAX request are being handled by separate thread, hence you can't get the $progress
value.
这有点困难,(仅供参考)PHP 进程和您的 AJAX 请求由单独的线程处理,因此您无法获得该$progress
值。
A quick solution: you can write the progress value to $_SESSION['some_progress']
every time it is being updated, then your AJAX request can get the progress value by accessing the $_SESSION['some_progress']
.
一个快速的解决方案:您可以在$_SESSION['some_progress']
每次更新时写入进度值,然后您的 AJAX 请求可以通过访问$_SESSION['some_progress']
.
You'll need JavaScript's setInterval()
or setTimeout()
to keep calling the AJAX handler, until you get the return as 100
.
您将需要 JavaScriptsetInterval()
或setTimeout()
继续调用 AJAX 处理程序,直到您获得作为100
.
It is not the perfect solution, but its quick and easy.
这不是完美的解决方案,但它快速简便。
Because you cannot use the same session twice at the same time, use a database instead. Write the status to a database and read from that with the interval'd AJAX call.
因为您不能同时使用同一个会话两次,所以请改用数据库。将状态写入数据库并使用间隔的 AJAX 调用从中读取。
回答by Ramon La Pietra
It's an old question but I had a similar need. I wanted to run a script with the php system()
command and show the output.
这是一个老问题,但我有类似的需求。我想用 phpsystem()
命令运行一个脚本并显示输出。
I've done it without polling.
我没有投票就做到了。
For Second Rikudoit case should be something like this:
对于 Second Rikudoit 案例应该是这样的:
JavaScript
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
<?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;
}
?>
回答by Silver Moon
Solutions are :
解决方案是:
Ajax polling - On the server side store the progress somewhere and then use a ajax call to fetch the progress at regular intervals.
Server sent events - An html5 feature that allows to generate dom events from output send by server. This is the best solution for such a case, but IE 10 does not support it.
Script/Iframe streaming - Use an iframe to stream output from the long running script which would output script tags as intervals that can generate some reaction in the browser.
Ajax 轮询 - 在服务器端将进度存储在某处,然后使用 ajax 调用定期获取进度。
服务器发送事件 - 一项 html5 功能,允许从服务器发送的输出生成 dom 事件。这是这种情况的最佳解决方案,但 IE 10 不支持它。
脚本/Iframe 流式传输 - 使用 iframe 从长时间运行的脚本中流式传输输出,该脚本将输出脚本标签作为可以在浏览器中产生一些反应的间隔。
回答by antoni
Here I just wanted to add 2 concerns on top of what @Jarod Law wrote above https://stackoverflow.com/a/7049321/2012407
在这里,我只想在@Jarod Law 上面写的内容之上添加 2 个关注点https://stackoverflow.com/a/7049321/2012407
Very simple and efficient indeed. I tweaked-and-used :) So my 2 concerns are:
确实非常简单和高效。我调整和使用 :) 所以我的两个问题是:
rather than using
setInterval()
orsetTimeout()
use a recursive call in callback like: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. }); }
As the calls are asynchronous in may come back in unwanted order otherwise.
saving into
$_SESSION['some_progress']
is smart and you can, no need a database storage. What you actually need is to allow both scripts to be called simultaneously and not being queued by PHP. So what you need the most issession_write_close()
! I posted a very simple demo example here: https://stackoverflow.com/a/38334673/2012407
而不是在回调中使用
setInterval()
或setTimeout()
使用递归调用,例如: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. }); }
由于调用是异步的,否则可能会以不需要的顺序返回。
保存到
$_SESSION['some_progress']
很聪明,你可以,不需要数据库存储。您真正需要的是允许同时调用两个脚本,而不是被 PHP 排队。所以你最需要的是session_write_close()
!我在这里发布了一个非常简单的演示示例:https: //stackoverflow.com/a/38334673/2012407
回答by Michael Petrov
Have you considered outputting javascript and using a stream flush? It would look something like this
您是否考虑过输出 javascript 并使用流刷新?它看起来像这样
echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>';
flush();
This output is sent immediately to the browser because of the flush. Do it periodically from the long running script and it should work beautifully.
由于刷新,此输出会立即发送到浏览器。从长时间运行的脚本中定期执行它,它应该可以很好地工作。
回答by PixelsTech
Frequently in web applications, we may have a request to the back end system which may trigger a long running process such as searching huge amount of data or a long running database process. Then the front end webpage may hang and wait for the process to be finished. During this process, if we can provide the user some information about the progress of the back end process, it may improve user experience. Unfortunately, in web applications, this seems not an easy task because web scripting languages don't support multithreading and HTTP is stateless. We now can have AJAX to simulate real time process.
经常在 Web 应用程序中,我们可能会向后端系统发出请求,这可能会触发长时间运行的进程,例如搜索大量数据或长时间运行的数据库进程。然后前端网页可能会挂起并等待该过程完成。在这个过程中,如果我们能够向用户提供一些后端流程的进展信息,可能会提升用户体验。不幸的是,在 Web 应用程序中,这似乎不是一件容易的事,因为 Web 脚本语言不支持多线程并且 HTTP 是无状态的。我们现在可以使用 AJAX 来模拟实时过程。
Basically we need three files to handle the request. The first one is the script which runs the actual long running job and it needs to have a session variable to store the progress. The second script is the status script which will echo the session variable in the long running job script. The last one is the client side AJAX script which can poll the status script frequently.
基本上我们需要三个文件来处理请求。第一个是运行实际长时间运行作业的脚本,它需要有一个会话变量来存储进度。第二个脚本是状态脚本,它将在长时间运行的作业脚本中回显会话变量。最后一个是客户端 AJAX 脚本,可以频繁轮询状态脚本。
For the details of the implementation, you can refer to PHP to get long running process progress dynamically
具体实现可以参考PHP动态获取长时间运行的进程进度