How to start and stop Server Side Event script from server - javascript

I'm following the WC3 tutorial on server side events and am interested to know if i can use them to only deliver events/notifications when a php script is actually running, i.e. executing code. Ideally i'd like to use server side events to show script execution progress and avoid server polling altogether.
Currently my very simple client code is as follows:
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("https://xyz.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
}
</script>
</body>
</html>
and my client code is as follows:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
function send_message()
{
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
}
send_message();
?>
I know the above is an extremely simple example however I can't figure out how to start and stop the notifications and, in turn only do this when a server side script is running - a simple server script example would be to only send notifications every second when a timer counts from 1 to 10 and then stop.
Is this at all possible?

Related

How to refresh the web page when index changes without lagging the server

I would like to refresh my static web page running on apache when the index changes. I've already tried to use server-side events, where I had a PHP file checking if the index changed and if yes, it sent the event to the webpage. This works exactly how I want, but there is a problem. Because the page is used by a lot of people sometimes (tens or up to a hundred opened tabs), it quickly starts to spam many apache processes. Then, it reaches the limit, and the apache freezes.
The question is how to handle this. If a user closes the tab, the process is killed, however, if not, the apache freezes.
The PHP script looks like this (it is checking two things, first, if the file chenged, or second if the status is something. As I said, this works fine, the problem is its lagging the server):
<?php
session_start();
session_write_close();
ignore_user_abort(false);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$filename = "index.html";
while(True){
if ( connection_aborted() ){
exit();
}else{
try{
$string = file_get_contents("current_status.json");
$json = json_decode($string, true);
$pom1 = $json["state"];
$t1 = shell_exec("date -r index.html");
sleep(3);
$pom2 = $json["state"];
if($t1 != shell_exec("date -r index.html")) {
sleep(2);
echo "data: file changed \n\n";
} else if($pom2=="ready") {
sleep(2);
echo "data: new shot available \n\n";
} else {
echo "heartbeat";
}
ob_flush();
flush();
}
catch (\Error $e){
echo "data: error, json not available \n\n";
}
}
}
ob_end_flush()
?>
Then, there is a classical javascript function in the index file with event source on the PHP file.
My question is, how can I do this to not make apache crashing? Can I somehow set up SSE to handle it? I know I can allow more processes on apache, but my resources are limited.
Is there any other way how to do this? For example, live.js works as well, but the problem is the sam, a lot of processes when opened multiple times.
Yes, I can see how this would put far more strain on your server than necessary.
What you should do is poll for changes from javascript. You send an asynchronous request for the last time the index file changed from your javascript. You do it once when the page loads and store the time. Then you check again on an interval and compare the result with the first one. If it changed you refresh the page.
The PHP script should get the last change date, output it and exit - no continuously running PHP scripts.
Here is what the PHP file should look like:
<?php
header('Content-type: text/plain');
echo filemtime('index.html');
?>
Keep this minimal. The built in filemtime function is more efficient than running shell_exec commands.
Here is an example javascript:
chk_index_change_time(false);
function chk_index_change_time(last){
fetch('http://yourdomain.com/yourpath/get_index_change_time.php')
.then(res => res.text())
.then((index_change_time) => {
if ((false !== last) && (last != index_change_time)){
location.reload();
} else {
setTimeout(chk_index_change_time, 3000, index_change_time);
}
});
}
Feel free to error handling or whatever, but this will work. I used a timeout here instead of interval so that if the server is slow the server response time doesn't come out of the 3 second delay.

Pushing data from one client to another using Php

I am making my college project on Air Quality Monitoring System, in that data (say some integer value) has to be taken from sensing unit to a webpage.
What I want
Is that the script called upon by this url http://localhost/AQProject/recordupdate.php?val=2 updates a web page displaying the content. Now I know I can save that data in database and run ajax based query every two seconds to check for update, but I want that update to be pushed by server.
What have I done:
I have tried Server sent events. Here's what i tried
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
if($_SERVER["REQUEST_METHOD"]=="GET")
{
if(empty($_GET["val"])) die ("Empty Value from source");
else
{
$v = $_GET['val'];
echo "data: The Pollution stub value is {$v}".PHP_EOL;
ob_flush();
flush();
}
}?>
and html has script
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("recordupdate.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data +
"<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
Now, I have figured out this much that (correct me If i am wrong) it won't work because when another client (sensing unit) calls for recordupdate.php its a different instance of that script than that called by webpage client.
Is there any possible way of doing this using server sent events? Or I absolutely need to dig into websockets, node.js etc. Thanks in advance
What you want to do is not quite as easy as you hoped, but this is still a job for which SSE is suited. You don't need to use sockets, and don't need to use ajax polling.
But you do need some database store, on the server, that can be shared by PHP scripts. As installing a LAMP stack is so easy, I'd recommend using MySQL, even though it might be overkill for what you need. But your database could be as simple as a text file.
(To keep the below samples as small as possible, I've assumed your DB will be /tmp/val.txt, and I've not done any file locking, or checking for bad data. Just be aware that you need to do some work before putting this in production in an untrusted environment. I'd recommend pre-creating /tmp/val.txt to avoid any noise about files not existing.)
Your recordupdate.php has the job to record the value it is given:
<?php
if($_SERVER["REQUEST_METHOD"]=="GET")
{
if(empty($_GET["val"])) die ("Empty Value from source");
else file_put_contents("/tmp/val.txt", $_GET['val']);
}
You then have sse.php, which web clients connect to:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$prev = '';
while(1){
$v = file_get_contents("/tmp/val.txt");
if($v != $prev){
echo "data: The Pollution stub value is {$v}\n\n";
$prev = $v;
}
usleep(100000); //0.1s
}
This script is checking the text file for changes 10 times a second. As soon as it spots one it sends it to the client. (Mean latency is 0.05s plus network overhead.) Sleep for less time if you need lower latency.
The only change to your front-end HTML is to call "sse.php" instead:
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("sse.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data +
"<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
HTTP is one way protocol. Request from client to server only. Yes, absolutely need to dig into websockets, node.js etc.

php response text empty when using password_hash or password_verify

I have an application that uses javascript to get a string and POST it to a receiver php file on the server for further processing. The receiver's job is to parse the string, pass things along, and report to javascript how stuff is going. Recently I've tried to add password-based security to this whole shebang, but now receiver.php passes back an empty response.
What I'm finding is that if I call password_verify anywhere in the receiver (no matter what I do with it... I could even just call it without using it) the following echos in the script don't run - any responseText from those echos will be empty - and I've no idea why. Though, if I just run the php script raw from command line, everything DOES work.
Before continuing, I would note that I am pretty new to web development and password-based security, so don't skewer me too hard. Good news, though - unlike every OTHER problem I can find on the web, I AM getting correct hashes and correct verify-responses.
Using php 5.6.23
A scaled down version of the JS file "test.html":
<!doctype html>
<html lang="en">
<head>
<script type="text/javascript">
function reviewAndSubmit() {
//bigOlString is usually built as a result of TONS of script on this page, use this as a test
var bigOlString = "password=1234 name=test otherParams=barglebargle"
var postString = new XMLHttpRequest();
//build function to receive post response
postString.onreadystatechange = function() {
if (postString.readyState == 4) {
window.alert("responseText: " + postString.responseText)
}
}
postString.open("POST", "receivertest.php", true);
postString.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
postString.send(bigOlString);
}
</script>
</head>
<body>
<button class="button" id="pushMe" onClick="reviewAndSubmit()">Why does PHP hate me so bad?</button>
And receivertest.php:
<?php
//hashed version of '1234' and debug name 'test'
$debugPass = '$2y$10$b.08/4NfawKOwrBYJqguc.AWsI3mQiGGaz1eYvfc9Uid1auQKKABm';
$debugName = 'test';
//get incoming string
$inString = file_get_contents('php://input');
//get challenge content
$challengePass = substr($inString, strpos($inString, "password=") + 9, strpos($inString, "name=") - 10); //10 because of the space
$name = substr($inString, strpos($inString, "name=") +5, (strpos($inString, "otherParams=") - strpos($inString, "name=") - 6)); //ugly!
//begin authentication
$auth = False;
echo $name;
echo $challengePass;
password_verify($challengePass, $debugPass); //yes, I'm not doing anything with this. Doesn't matter.
echo "this line won't echo";
?>
If you comment out the 'password_verify' line in receivertest.php, everything echos perfectly. If you don't, no luck - the 'alert' window in test.html just spits out 'responseText test123'. If you run receivertest.php in console (php receivertest.php), however, everything echos correctly.
Why is my responseText empty?
EDIT: I've edited the php script to better illustrate the problem. Yes, I know I'm not USING password_verify for anything. It doesn't matter. The 'this line won't echo' line doesn't echo out in test.html like the other lines do. My question is: why NOT?
The code is all fine. I made a quick edit in my httpd.conf file and restarted apache, and everything started working. I undid that edit to check and restarted - everything still worked. So I guess all I needed was an apache restart.
Apache seems to have more to do with php than I thought, I guess?

Detecting user abort/navigation websockets

I'm experimenting with websockets and would like to detect if a user has navigated away from the page to the PHP stream script from running. Everything else is working fine.
Everything I try to do does not stop the PHP script from running (using Xampp locally).
I'm currently working with javascript's beforeunload, and PHP's connection_aborted(), connection_status() and even file_exists() (which is the only one that works currently!!!).
Until the script exists or I restart apache I cannot reload the page but it's the abort detection that I have to get working. Firebug reports the socket script has aborted but PHP just keeps ion running. Any help appreciated.
My javascript looks like this
function socket_open(){
if(!!window.EventSource){
var websocket = new EventSource('./ajax/progress.php');
websocket.addEventListener('message', function(e){
var data = JSON.parse(e.data);
console.log(data);
},false);
$(window).bind('beforeunload', function(){
websocket.onclose = function(){};
websocket.close();
});
}
}
And my PHP is like this.
./ajax/progress.php
function get_task_progress(){
$output = array();
//conditionally create some output
if(!empty($output)){
echo 'data: '.json_encode($output)."\n\n";
ob_flush();
flush();
}
sleep(1);
if(!connection_aborted() && connection_status()==0 && file_exists('1.txt')){
get_task_progress();
}
}
I seem to have solved it but echoing and flushing a no updates status on every request before the test for a live connection
if(!empty($output)){
$tasks_state = $tasks_new;
echo 'data: '.json_encode($output)."\n\n";
}else{
echo 'data: '.json_encode(array('e'=>0))."\n\n";
}
ob_flush();
flush();
sleep(1);
if(!connection_aborted() && connection_status()==0){
get_task_progress();
}

PHP: Outputting the system / Shell_exec command output in web browser

I tried to output a simple ping command on a web page in a similar way( and same time) as it is displaying in terminal, using shell_exec; But it is displaying only after the complete execution, while I needed it to display whenever it is displaying on terminal, My code is
<?php
$i= shell_exec("ping -c 4 google.com");
echo "<pre> $i <pre>";
?>
It is waiting for a while and the dumping the whole thing on a single shot.. can PHP recognize the outputting of each line and display it on the web page
EDIT I tried this also
<?php
$proc = popen("ping -c 4 google.com", 'r');
echo '<pre>';
while (!feof($proc)) {
echo fread($proc, 4096);
}
echo '</pre>';
?>
But still I gets the same result..
EDIT When I tried to execute this PHP code in terminal , ( php test.php) it is working properly in the same way it gives when we directly do ping on server. but in web page it is still the same.
Uhm, strange behavior from the web browser. I'm using this code:
<?php
ob_end_flush();
ini_set("output_buffering", "0");
ob_implicit_flush(true);
function pingtest()
{
$proc = popen("ping -c 5 google.com", 'r');
while (!feof($proc))
{
echo "[".date("i:s")."] ".fread($proc, 4096);
}
}
?>
<!DOCTYPE html>
<html>
<body>
<pre>
Immediate output:
<?php
pingtest();
?>
</pre>
</body>
</html>
In the browser the content appears after all bytes has been received.
But, the content is actually delivered on time, do this test:
wget -O - -q "http://localhost/ping.php"
You will see that the response is delivered by php & apache2 on time.
I'm using this kind of execution on long task for a while, but using a more complex solution:
an html file for interface
a php file that run the long task
Connect html interface with php long execution using EventSource object (available on html5)
interface (test.html)
<!DOCTYPE html>
<html>
<head>
<title>Simple EventSource example</title>
</head>
<body>
<script type="text/javascript">
function eventsourcetest() {
var ta = document.getElementById('output');
var source = new EventSource('test.php');
source.addEventListener('message', function(e) {
if (e.data !== '') {
ta.value += e.data + '\n';
}
}, false);
source.addEventListener('error', function(e) {
source.close();
}, false);
}
</script>
<p>Output:<br/><textarea id="output" style="width: 80%; height: 25em;"></textarea></p>
<p><button type="button" onclick="eventsourcetest();">ping google.com</button>
</html>
Server Side Component (test.php)
<?php
ob_end_flush();
ini_set("output_buffering", "0");
ob_implicit_flush(true);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
function echoEvent($datatext) {
echo "data: ".implode("\ndata: ", explode("\n", $datatext))."\n\n";
}
echoEvent("Start!");
$proc = popen("ping -c 5 google.com", 'r');
while (!feof($proc)) {
echoEvent(fread($proc, 4096));
}
echoEvent("Finish!");
Put both files in one place on a webserver and enter test.html, I think this is what you are looking for from the beginning.
Use output buffering and flush. You might also want to look into the Symfony 2 process component.
Its not a PHP matter, or rather its a shared matter between php and the browser.
In PHP: Make sure output buffering is off, you can do this by running ob_end_clean() before outputting anything.
As this SO post suggests you have to either pad the very first string outputted to 512 bytes OR specify a charset encoding via http header. The padding solution may very well be the easiest way around this, its basically this: echo(str_pad("Live Ping Test!",512)); and then start echoing the result of your fread.
You might want to try using flush() to flush the output as and when its ready, and use passthru() to execute the command.
Carlos C Soto is right, you have to use javascript. EventSource is the way to go. Basically, it's javascript code that will constantly call a url
You can write the output of ping in a file, and write a php script that will read the last line, then call this script with eventsource.
Search "Server Sent Events" on the web to find more examples
if can resolve using apache execution user. if your root user is diffrent and server user different then it will not allow to execute command line command.
I tested Carlos's answer on my side...
and I HAD to add flush();ob_flush(); for it to work properly (both needed flush AND ob_flush)
like this
<?php
$proc = popen("ping -c 5 google.com", 'r');
while (!feof($proc))
{
echo "[".date("i:s")."] ".fread($proc, 4096).'<br>';flush();ob_flush();
}
?>

Categories