I'm attempting to stream php data to an ajax handler and display it on the page using the code below, but on some devices the results being echoed by the page are't being displayed. I've used wireshark to confirm that the data is being received by the host (can see it in the tcp packets) but it doesn't seem to make it to ajax? What could possibly be going wrong?
Client side:
$.ajax(
{
xhrFields: {
onprogress: function(e)
{
var this_response, response = e.currentTarget.response;
if(last_response_len === false)
{
this_response = response;
last_response_len = response.length;
}
else
{
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
/* Put output on page */
$('#tool-output-area').append(this_response + '<br>');
}
},
method: 'POST',
url: '../../../../../../../../page.php',
data:
{
data-key: data-variable...
}
})
.done(function(data)
{
// stuff
})
.fail(function()
{
// stuff
});
});
Server Side:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
/* Set Maximum Execution Time By Plan */
ini_set('max_execution_time', getMaxExeTime());
ini_set('output_buffering', 'off');
ini_set('zlib.output_compression', false);
ini_set('implicit_flush', true);
ob_implicit_flush(true);
while (ob_get_level() > 0)
{
$level = ob_get_level();
ob_end_clean();
if (ob_get_level() == $level) break;
}
if (function_exists('apache_setenv'))
{
apache_setenv('no-gzip', '1');
apache_setenv('dont-vary', '1');
}
// Checks
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "proc.txt", "a")
);
$pipes = array();
$process = proc_open('exec ' . escapeshellcmd($tool_command), $descriptorspec, $pipes, '/path-here', null);
$proc_details = proc_get_status($process);
$pid = $proc_details['pid'];
$proc_row = logProcess($pid, $tool_command, $output_file, $tool);
$str = '';
session_write_close();
if(is_resource($process))
{
while ($curStr = fgets($pipes[1]))
{
echo nl2br(htmlentities($curStr)); // Send the data back
flush();
$str .= nl2br(htmlentities($curStr));
$arr = proc_get_status($process);
sleep(1);
}
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
Related
I have a simple php page that streams ping results. It works fine but when the user closes the connection, the ping command keeps running in the background. I used both ignore_user_abort() and register_shutdown_function(), but none worked.
It is running on OpenWRT with uhttpd.
this is my code in a class-based approach.
class process
{
private $proc;
private $pid;
private $descriptorspec;
function start($cmd)
{
$cwd = '/tmp';
$this->proc = proc_open($cmd, $this->descriptorspec, $pipes, $cwd);
$this->pid = proc_get_status($this->proc)['pid'];
session_write_close();
ignore_user_abort(true);
set_time_limit(0);
ini_set('zlib.output_compression', 0);
ini_set('implicit_flush', 1);
while (!feof($pipes[1])) {
echo ">" . fread($pipes[1], 4096);
flush();
ob_flush();
while (ob_get_level()) {
ob_end_clean();
}
if (connection_status() != CONNECTION_NORMAL) {
shell_exec("killall ping &");
break;
}
}
pclose($this->proc);
}
function __construct()
{
register_shutdown_function(array($this, "onShutdown"));
$this->descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file", "/tmp/app/proc.error.output.txt", "a") // stderr is a file to write to
);
}
function onShutdown()
{
shell_exec("killall ping &");
}
function __destruct()
{
shell_exec("killall ping");
// posix_kill($this->pid, SIGKILL);
// proc_terminate($this->proc);
// pclose($this->proc);
}
}
This is my function-based approach.
global $proc, $pid;
while (# ob_end_flush()) ;
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file", "/tmp/app/proc.error.output.txt", "a") // stderr is a file to write to
);
$cwd = '/tmp';
$proc = proc_open($cmd, $descriptorspec, $pipes, $cwd);
$pid = proc_get_status($proc)['pid'];
session_write_close();
ignore_user_abort(true);
set_time_limit(0);
register_shutdown_function('on_shutdown');
while (ob_get_level()){
ob_get_contents();
ob_end_clean();
}
//ob_start();
while (!feof($pipes[1])) {
echo fread($pipes[1], 4096);
flush();
ob_flush();
if (connection_aborted() != 0) {
posix_kill($pid, SIGKILL);
pclose($proc);
}
}
//ob_end_flush();
pclose($proc);
Check phpinfo for the environment setup, set PATHs, if shell_exec is disabled, or FPM runs in a chroot.
I'm developing a little GUI to interact with mpsyt through browser. I wrote this script:
<?php
file_put_contents("running.txt", true);
$cwd = '/var/www/html';
$pipes = array();
$descriptors = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w"));
$process = proc_open("sudo mpsyt", $descriptors, $pipes, $cwd);
$info = proc_get_status($process);
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
$terminate = false;
while (!$terminate) {
$command = file_get_contents("command.txt");
if ($command !== "") {
if ($command == "terminate") {
$terminate = true;
}
if ($command == "pause") {
fwrite($pipes[0], " "); //<---HERE
} else {
fwrite($pipes[0], $command);
}
fflush($pipes[0]);
file_put_contents("command.txt", "");
}
if (($txt = stream_get_contents($pipes[1])) != "") {
$txt = preg_replace('#\R+#', '</p><p>', $txt);
file_put_contents("content.txt", $txt);
}
usleep(100000);
}
file_put_contents("content.txt", "");
fclose($pipe[0]);
fclose($pipe[1]);
proc_close($process);
file_put_contents("running.txt", false);
The problem is that mpsyt require to press spacebar to pause a song. How can i simulate this action with fwrite() (or else)?
Googling, I found this thread (same question):
https://github.com/mps-youtube/mps-youtube/issues/568
with this advertisement:
https://github.com/mpv-player/mpv/commit/a037f7b
Is there any way to bypass this situation?
Thanks in advance!
P.S. I also tried to send the SIGINT signal (if you press Ctrl-C while is playing a song, it will stop) but it seems the process ignores it.
In timer.php I have this:
$handle = fopen( 'php://stdout', 'wa' ) ;
$unusedEvTimerObject = new EvTimer(0, 1, function ($watchercallback) use ($handle) { //create & call timer
echo "=>".(Ev::iteration() % 60)."<=";
fwrite( $handle, "Hello World! \n");
} );
Ev::run();
fclose( $handle );
And in child.php I have this:
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a")
);
$process = proc_open('php ', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "<? include('app/timer.php'); ?>");
fclose($pipes[0]);
$output = "";
while (!feof($pipes[1])) {
$output .= fgets($pipes[1]);
};
fclose($pipes[1]);
$return_value = proc_close($process);
echo "command returned $return_value\n";
}
If I invoke timer.php direct with
$php app/timer.php
the output I get is "=>1<=Hello World! =>2<=Hello World!"
but if I invoke with $php app/child.php
I get no output whereas I'd expect stdout from timer.php to redirect to child.php and be printed by it.
I'm flailing a bit & I'm guessing child.php is not getting any input but I can't see why. HALP!
The $output isn't printed in the sample code.
while (!feof($pipes[1])) {
echo fgets($pipes[1]);
};
Calling $> php child.php then prints the timer output
This is a server client web socket program, when client send "exe" command the server executes a C application.
The output of the c application is read from STDOUT by server and displayed on the client browser.
Now there is a C application that requires a user input(scanf), we saved the input in a txt file. We read the input
and write it to the stdin of the c program. The problem is that it seems that the C program is not accepting the input
that we are writting on the stdin. When i tried this code without any stream select it was working and displaying the sum of two numbers read from input file, but when i place it in stream select command its creating problem. My complete server code is at the end.
a part of server code that is creating child process on a client request
if(strcmp($user_message, "exe") == 0 ) {
echo "Executing a process\n";
$cwd = '/var/www/html/test/websockets' ;
$process = proc_open($exe_command, $descriptorspec, $pipes, $cwd);//creating child process
sleep(1);
if (is_resource($process))
{
echo "Process Created";
$read_socks[] = $pipes[1];//add a descriptor
$stdout = array($pipes[1]);//save stdout in a variable defined above
$stdin = array($pipes[0]);
print_r ($stdout);
print_r ($stdin);
}
}
else
{
echo "Passing value to the C program".$user_message;
//Read input.txt by line and store it in an array
$input = array();
$input = file('/var/www/html/test/websockets/input.txt');
echo "INPUT";
print_r($input);
echo "\n";
//Feed the input (hardcoded)
$bytes = fwrite($stdin[0], "$input[0] $input[1]");
echo "Bytes written:".$bytes;
sleep(1);
}
}
}
add.c
#include <stdio.h>
int main(void)
{
int first, second;
printf("Enter two integers > \n");
scanf("%d", &first);
scanf("%d", &second);
printf("The two numbers are: %d %d\n", first, second);
printf("Output: %d\n", first+second);
}
input.txt
2
4
output
Passing value to the C program3Array
(
[0] => Resource id #11
)
INPUTArray
(
[0] => 2
[1] => 4
)
Bytes written:5
By doing ps I get
root 1173 1164 0 12:08 pts/6 00:00:00 tclsh /usr/bin/unbuffer /var/www/html/test/websockets/./add
root 1174 1173 0 12:08 pts/8 00:00:00 /var/www/html/test/websockets/./add
After bytes written the server is hanging there is no response from the C application i.e the addition result of 4+2.
Why there are two process of add i started 1 with proc open.
complete server code
<?php
execute_prog('unbuffer /var/www/html/test/websockets/./add');//unbuffer stdout
function execute_prog($exe)
{
echo "[+execute_prog]";
$host = 'localhost'; //host
$port = '9000'; //port
$null = NULL; //null var
$read_socks;
$new_client;
$server = stream_socket_server("tcp://0.0.0.0:9000", $errno, $errorMessage);
if ($server === false)
{
throw new UnexpectedValueException("Could not bind to socket: $errorMessage");
}
set_time_limit(1800);
$exe_command = escapeshellcmd($exe);
$descriptorspec = array(
0 => array("pipe", "r"), // stdin -> for execution
1 => array("pipe", "w"), // stdout -> for execution
2 => array("pipe", "w") // stderr
);
// $process = proc_open($exe_command, $descriptorspec, $pipes);//creating child process
// if (is_resource($process))
{
$client_socks = array();
$read_socks = array($server);
$changed = array();
$stdout = NULL;
$stdin = NULL;
while(1)
{
//prepare readable sockets
$write = NULL;
$err = NULL;
$except = NULL;
$changed = $read_socks;//by refrence
/*
echo "stdout:";
print_r ($stdout);
echo "\n";
echo "changed:";
print_r ($changed);
echo "\n";
echo "read sock:";
print_r ($read_socks);
echo "\n";
*/
if (false === ($num_changed_streams = stream_select($changed, $write, $except, 0)))
{
/* Error handling */
echo "Errors\n";
}
else if ($num_changed_streams > 0)
{
/* At least on one of the streams something interesting happened */
echo "Data on ".$num_changed_streams." descriptor\n";
if(in_array($stdout[0], $changed))
{
echo "Data on child process STDOUT\n";
$s = fgets($stdout[0]);
if( $s === false )
{
// Hello program has finished.
echo 'Finished', PHP_EOL;
$s = NULL;
//ob_flush();
flush();
// Close all descriptors and return...
// break;
}
else
{
echo $s."</br>";
//prepare data to be sent to client
$response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$s, 'color'=>$user_color)));
foreach ($read_socks as $sock)
{
if(($sock != $server) && ($sock != $stdout))
fwrite($sock, $response_text, strlen($response_text));
}
$s = NULL;
//ob_flush();
flush();
}
}
else if(in_array($server, $changed))
{
//new client
echo "New Connection\n";
$new_client = stream_socket_accept($server);
if ($new_client)
{
//print remote client information, ip and port number
echo 'Connection accepted from ' . stream_socket_get_name($new_client, true) . "n";
$read_socks[] = $new_client;
echo "Now there are total ". count($read_socks) . " clients.n";
}
$header = fread($new_client, 1024);//read data sent by the socket
perform_handshaking($header, $new_client, $host, $port); //perform websocket handshake
$ip = stream_socket_get_name($new_client, true);
$response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); //prepare json data
fwrite($new_client,$response,strlen($response));
//delete the server socket from the read sockets
unset($changed[ array_search($server, $changed) ]);
}
else if($write)
{
echo "Data on child process STDIN\n";
}
else if($err)
{
echo "Data on child process STDERR\n";
}
else
{
echo "Message from the client \n";
//message from existing client
foreach($changed as $sock)
{
$data = fread($sock, 128);
//echo "Data read:".$data." From sock:".$sock."\n";
if(!$data)
{
unset($client_socks[ array_search($sock, $client_socks) ]);
#fclose($sock);
echo "A client disconnected. Now there are total ". count($client_socks) . " clients.n";
continue;
}
else
{
$received_text = unmask($data); //unmask data
$tst_msg = json_decode($received_text); //json decode
$user_name = $tst_msg->name; //sender name
$user_message = $tst_msg->message; //message text
$user_color = $tst_msg->color; //color
//echo "name:".$user_name." user mesg:".$user_message."\n";
//prepare data to be sent to client
$response_text = mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message, 'color'=>$user_color)));
fwrite($sock, $response_text, strlen($response_text));
//..................................................................................................................
if(strcmp($user_message, "exe") == 0 )
{
echo "Executing a process\n";
$cwd = '/var/www/html/test/websockets' ;
$process = proc_open($exe_command, $descriptorspec, $pipes, $cwd);//creating child process
sleep(1);
if (is_resource($process))
{
echo "Process Created";
$read_socks[] = $pipes[1];//add a descriptor
$stdout = array($pipes[1]);//save stdout in a variable defined above
$stdin = array($pipes[0]);
print_r ($stdout);
print_r ($stdin);
}
}
else
{
echo "Passing value to the C program".$user_message;
print_r ($stdin);
echo "\n";
//$input = array($user_message);
// fwrite($stdin[0],"$input[0]");//,strlen($user_message));
//Read input.txt by line and store it in an array
$input = array();
$input = file('/var/www/html/test/websockets/input.txt');
echo "INPUT";
print_r($input);
echo "\n";
//Feed the input (hardcoded)
$bytes = fwrite($stdin[0], "$input[0] $input[1]");// $input[1]");
echo "Bytes written:".$bytes;
}
}
}
}
$num_changed_streams = 0;
}
}
// close the listening socket
fclose($server);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
echo "exitcode: ".proc_close($process)."\n";
}
echo "[-execute_prog]";
// return $ret;
}
?>
Any ideas how to solve this?
Thank you!
EDIT
The following code writes to the stdin of a C application but when i tried to integrate in the above client server its not working.
<?php
//descriptors to be handled by parent
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/var/www/html/websockets/error.txt", "a")
);
// define current working directory where files would be stored
$cwd = '/var/www/html' ;
$child_proc = escapeshellcmd("unbuffer /var/www/html/test/websockets/./add");
// $process = proc_open('/var/www/html/websockets/add', $descriptorspec, $pipes);//creating child process
$process = proc_open($child_proc, $descriptorspec, $pipes, $cwd);//creating child process
sleep(1);
if (is_resource($process))
{
//Read input.txt by line and store it in an array
$input = file('/var/www/html/test/websockets/input.txt');
//Feed the input (hardcoded)
fwrite($pipes[0], "$input[0] $input[1]");
fclose($pipes[0]);
while ($s = fgets($pipes[1]))
{
print $s."</br>";
flush();
}
?>
Any guess why the code in the edit part is working where as when integrated in the client server application its output is not as expected.
$child_proc = escapeshellcmd("unbuffer /var/www/html/test/websockets/./add")
the keyword unbuffer was causing the problem although it was there for some other reason but it was the one creating problem.
I am executing a TCL script from PHP using proc_open.
I first open the TCL shell
2) Send a command using fwrite
3) What I need is fread to wait/block until the
command sent by fwrite is complete
and get all the contents .The command may take some time to complete.
(I am able to read just 2 lines and then it is going off to the next loop)
Can someone guide me.
The present code is
<?php
$app = 'tclsh84';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/wamp/www/tcl/bin/g.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process))
{
for($i=0;$i<4;$i++)
{
fwrite($pipes[0], 'source c:/wamp/www/tcl/bin/test.tcl'."\n");
$content= fread($pipes[1],8192)
print "$content";
}
fclose($pipes[0]);
fclose($pipes[1]);
proc_close($process);
}
?>
I'm thinking about a combination of
stream_select and/or feof()
fread() and concatenation of the partial results ($result .= fread())
and maybe proc_get_status() to determine the end of the process
You want to wait until the tcl application doesn't write something to its stdout for a certain amount of time (presuming that this means the end of the last command) and then send the next command/line to its stdin?
edit:
Seems like you can send all commands to the tcl shell at once and they are processed one by one, i.e. the shell reads the next input line/command when it's done with the previous one. I've tested this with the script.
incr a 1
after 1000
puts [concat [clock seconds] $a]
and
<?php
$app = 'c:/programme/tcl/bin/tclsh85.exe';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/god.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "set a 1\n");
for($i=0;$i<4;$i++) {
fwrite($pipes[0], "source c:/helloworld.tcl\n");
}
// when all scripts are done the shell shall exit
fwrite($pipes[0], "exit\n");
fclose($pipes[0]);
do {
$read=array($pipes[1]); $write=array(); $except=array($pipes[1]);
// wait up to 1 second for new output of the tcl process
$ready = stream_select($read, $write, $except, 1, 0);
if ( $ready && $read /* is not empty */) {
// get the partial output
$r = fread($pipes[1], 2048);
echo $r;
}
// is the process still running?
$status = proc_get_status($process);
} while($status['running']);
fclose($pipes[1]);
proc_close($process);
}
?>
You probably want to add some more error handling. E.g. if stream_select() returns x times with an timeout something might have gone wrong.
edit2:
Let the shell print something you can scan for after each script.
<?php
// something that's not in the "normal" output of the scripts
$id = 'done'. time();
$app = 'c:/programme/tcl/bin/tclsh85.exe';
$descriptorspec = array(
0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","C:/god.txt","w")
) ;
$process = proc_open($app, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "set a 1\n");
for($i=0;$i<4;$i++) {
$output = '';
$continue = true;
$cTimeout = 0;
echo 'loop ', $i, "\n";
fwrite($pipes[0], "source c:/helloworld.tcl\n");
fwrite($pipes[0], "puts $id\n");
echo "waiting for idle\n";
do {
$read=array($pipes[1]);
$write=array();
$except=array($pipes[1]);
$ready = stream_select($read, $write, $except, 1, 0);
if ( $ready && $read ) {
$output .= fread($pipes[1], 2048);
// if the delimiter id shows up in $output
if ( false!==strpos($output, $id) ) {
// the script is done
$continue = false;
}
}
} while($continue);
echo 'loop ', $i, " finished\n";
}
proc_close($process);
}
?>
Try:
$content = '';
while(!feof($pipes[1]))
{
$content .= fread($pipes[1],8192);
}
Does that wait?