Note: this post have difference with this post whose accepted answer just read each line a time.
I have to slice 3D models for 3D printing on the server side, the process will cost some time. So I have to show the process for the user, I use the redis to store the process. I want to refresh the process every 0.5 seconds.
For example, sleep 0.5 sec, read all the content in the pip and process it each time.
For now I have tryed the below two, the first one will hold until it finished. the second use while is not a proper way, it will keep writing the redis will cause the client read process request hold to the end.
I've tryed these two:
The first will hold until the command finished.
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w") //here curaengine log all the info into stderror
);
$command = './CuraEngine slice -p -j ' . $fdmprinterpath . ' -j ' . $configpath . ' -o ' . $gcodepath . ' -l ' . $tempstlpath;
$cwd = '/usr/local/curaengine';
$process = proc_open($command, $descriptorspec, $pipes, $cwd);
if(is_resource($process))
{
print stream_get_contents($pipes[1]); //This will hold until the command finished.
}
and the second implemented as this post will each time one line.
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w") //here curaengine log all the info into stderror
);
$command = './CuraEngine slice -p -j ' . $fdmprinterpath . ' -j ' . $configpath . ' -o ' . $gcodepath . ' -l ' . $tempstlpath;
$cwd = '/usr/local/curaengine';
$process = proc_open($command, $descriptorspec, $pipes, $cwd);
if(is_resource($process))
{
while ($s = fgets($pipes[1])) {
print $s;
flush();
}
}
use fread()
to replace fgets()
.
Take a look at the Symfony component Process
. It provides easy async process, and real-time process output, among other things. For example:
$process = new Process($command);
$process->start('processOutput');
while ($process->isRunning()) {
usleep(500000);
$progress = $process->getIncrementalOutput();
progressOutput($progress);
}
$output = $process->getOutput();
//
// Example implementations:
//
function progressOutput($progress) {
echo $progress;
ob_flush();
}
function processOutput($type, $output) {
if ($type == Process::OUT) {
echo $output;
ob_flush();
}
}
You should disable output buffering first by adding this line to the start of your code:
while(@ob_end_clean());
With that line added, your second code should be able to stream.
Unfortunately, even with output buffering disabled, the result of output streaming is browser dependent.
I tested several browsers, both desktop and mobile,
and found that output streaming works in Chromium-based browsers, but doesn't work in Firefox.
Because it is browser dependent, the functions you use, be it fgets()
or fread()
, are irrelevant.
If you need the streaming to work on other browsers as well, you can encapsulate it with XMLHttpRequest.
Here is an example that works with Chromium-based browsers:
<?php
//stream.php
//disable output buffering
while(@ob_end_clean());
ob_implicit_flush(true);
if(empty($_SERVER['QUERY_STRING'])){
//simulate a lengthy process
file_put_contents(__FILE__.'.txt',$z='');
for($i=0; $i<10; $i++){
sleep(1);
echo $s = "<div>{$i}</div>\n";
file_put_contents(__FILE__.'.txt',$z.=$s);
}
}else{
//delay 500ms
usleep(500000);
echo file_get_contents(__FILE__.'.txt');
}
The HTML counterpart makes it work with other browsers:
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width, initial-scale=1" charset="utf-8"></head>
<body>
<div id='stream'></div>
<script>
var active = true;
xhr('/stream.php',function(){ active = false; });
update();
function update(r){
if(r) document.getElementById('stream').innerHTML = r.responseText;
if(active) xhr('/stream.php?stream',update);
}
function xhr(url,callback){
var r = new XMLHttpRequest();
r.open('GET',url);
r.onreadystatechange = function(){ if(r.readyState==4) callback(r); };
r.send();
}
</script>
</body>
</html>
You can use the same mechanism to create a progress bar like I mentioned.