Using EventSource with Ajax - javascript

I am trying to work out how EventSource might help solve an update problem with a web applications.
I would like to be able to notify visitors to a web page when one or another visitor has performed an update. The idea is:
One visitor makes some changes to a text area and submits.
The data is submitted using Ajax.
At the server, the changes are saved.
I would like the server to send a notification that changes have been made
I would like all other visitors to get this notification.
On the server in PHP, I have something like this:
if(isset($_POST['save'])) {
// process changes
$data = sprintf('data: {"time": "%s","type": "save"}',$date);
header("Cache-Control: no-store");
header("Content-Type: text/event-stream");
print "event: ping\n$data\n\n";
ob_flush();
flush();
exit;
}
In JavaScript I have something like this:
var eventSource = new EventSource("https://example.net/ajax.php", { withCredentials: true } );
eventSource.addEventListener("ping", (event) => {
const time = JSON.parse(event.data).time;
const type = JSON.parse(event.data).type;
console.log(`Ping at ${time} for ${type}`);
});
That doesn’t do the job at all. At the client end I get get an error message about the wrong headers (I think that it’s because there’s no save yet, so the PHP falls through to an empty string).
All the samples I have seen include a while(true) loop in the PHP, but non have a one-off event.
Is this possible to implement, and, if so, how?

Related

How to continue PHP script after sending HTTP response

I was wondering if it was possible to send HTTP response immediately and continue the script.
Background: Among all the petition I make to the server there's one that creates an Excel file (using PHPSpreadSheet), since creating this files can take a little longer I was thinking of responding a HTTP 202 status code to the Client, something like:
header("HTTP/1.1 202 Excel file in process");
and program in JavaScript a listener on the main js file for every time that status code arrives to activate an interval (setInterval()) and ask every certain amount of seconds whether the Excel file is ready or not
$(document).ajaxSuccess(function ( event, xhr, settings) {
if(xhr.status === 202) {
//Activate the setInterval function to ask the server every 10 seconds whether the file is ready or not
}
});
(I know I have to be activating/deactivating the interval)
So whenever I receive a petition on my createExcel.php file I would like to respond the user immediately that the petition has being received and start the making of such Excel file(s)
Something like:
<?php
//Tell user that the petition has being received
header("HTTP/1.1 202 Excel file in process");
//Kill this process or close this connection but continue executing
//Call createExcel.php
the createExcel.php file would update some table in the database the confirm the file has been created, same table that the interval petition will be consulting every 10 seconds
That's what I'm attempting to do, I would just like you guys to tell me how to call another another file without waiting for such called file to finish to respond the user.
I was thinking of using exec() but I have never used it (I'm testing it right after I post this), and most importantly any experience or tips would be greatly appreciated (like optimization tips and the like)
I saw this question here on Stack Overflow, but the answer suggests to create a cron service which is not a solution for me.
Thank you!
Edit---
Hey in case someone sees this I found two solutions to my question:
The first one I tried but gave a lot of trouble with permissions is this: https://code-boxx.com/php-background-process/
But this one would work beautifully if you run it from cmd, but when you run it thought the browser, Apache forbids you from using executing commands; so exec(), popen(), and similar commandss won't work unless you change your permissions in your folders, which I consider a security issue, so I found out this very beautiful function fastcgi_finish_request()
Edit 2 - solution
https://qastack.mx/programming/15273570/continue-processing-php-after-sending-http-response
this works we flush all content in buffer and close the connection and then we just continue the execution of the script.
In JS:
$(document).ajaxSuccess(function ( event, xhr, settings) {
if(xhr.status === 202) {
//console.log("Your file is in process");
//console.log(xhr.responseJSON);
//Activate interval to check if file is ready
}
});
//Make petition
$.post("petitionsHandler.php", {"texto":"Hello world, greeting from México!"}, function (resp){
//Handle responses
}, "json").fail(function () {
//Handle errors
});
In PHP
<?php
$text = $_POST["text"];
//If you are using sessions don't forget to close the session file
//Or such will be blocked until long script finishes
session_write_close();
header("HTTP/1.1 202 Solicitud recibida"); #This is the code I wanted to send
header("Content-Type: application/json"); #Depends the kind of data you're sending to the client
// Buffer all upcoming output...
ob_start();
// Send your response.
echo '{"success":true, "message":"Your request has been received."}';
// Get the size of the output.
$size = ob_get_length();
// Disable compression (in case content length is compressed).
//header("Content-Encoding: none"); #I didn't need this but check your situation
// Set the content length of the response.
header("Content-Length: {$size}");
// Close the connection.
header("Connection: close");
// Flush all output.
ob_end_flush();
ob_flush();
flush();
//All outputs have now been sent to the client
//You can continue executing your long tasks
include_once('createExcel.php');
createExcel.php
<?php
sleep(10); #You can use to checkthat the code works
//The above code will respond the client immediately and after ten seconds the Excel file will be created
require "PHPSpreadSheet/vendor/autoload.php";
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', $text);
$writer = new Xlsx($spreadsheet);
$writer->save('MyFile.xlsx');
Of course you have to make validations, so you don't have lots of running process in background but I hope the general idea has been shown in this example.
This is not exactly the code I use, but this is all it takes to continue executing code after responding the client.
In my own opinion, I thinks this is better than using exec() or similar functions which invoke the command terminal as that could be a potential vulnerability , so you don't have to change permissions or anything.
Note: If you're using sessions, Please remember to use session_write_close() because on heavy tasks will block the file until such task is finished.
I hope it helps :)
My answer was based on this blog: https://qastack.mx/programming/15273570/continue-processing-php-after-sending-http-response

JavaScript EventSource listening for a Webhook pushed to PHP script

I've been searching and experimenting and I just can't seem to figure this out. I would appreciate any insight. Thank you!
So I have a Shopify store and I have an Webhook that triggers when I make a sale, meaning it pushes JSON data of that sale to a PHP script on my server. Right now I'm having that PHP script insert the relevant data into a database and mark that sale as "unread." Then, I would have a separate HTML/JavaScript page that I would run separately, polling the server to check for unread sales ever 10 seconds or so. There's a little more to that, but that's the general idea. It's clunky and I would like to modernize this.
Here's what I've been trying and can't seem to get working.
Set up an EventSource page that's listening to a separate PHP script.
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sales Notification!</title>
</head>
<body>
<div id="widget">
<div id="notification_box"></div>
</div>
<script type="text/javascript">
if (!!window.EventSource) {
var source = new EventSource('alert_listener.php');
} else {
console.log("Window.EventSource fail!");
}
source.addEventListener('message', function(e) {
console.log(e.data);
}, false);
source.addEventListener('open', function(e) {
// Connection was opened.
}, false);
source.addEventListener('error', function(e) {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
}, false);
</script>
</body>
</html>
That actually works well when I do something simple like this example. Anyway, here's my PHP code listening for the JSON sent from the Websocket (some of this code is provided by Shopify):
<?php
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
error_reporting(0);
define('SHOPIFY_APP_SECRET', 'NOT_PUBLIC_HAR_HAR');
function verify_webhook($data, $hmac_header) {
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, SHOPIFY_APP_SECRET, true));
return ($hmac_header == $calculated_hmac);
}
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);
while (1) {
if ($data !== "") {
sendMsg("test id", "Data: " . $data);
}
sleep(1);
}
?>
When I push data from Shopify to this PHP script, the first JavaScript example is supposed to be listening for this data, but nothing happens. Not a thing. I have no idea why. I have no idea how to tell the PHP script to say, "Hey! An order came in! Yo JavaScript, do something!" and then the JavaScript go, "New order received! Let's do a thing!"
TLDR:
Shopify pushes JSON to a PHP script (via a webhook)
Separate JavaScript file listens to this PHP script via EventSource object and reacts accordingly
I can't get this to work. Please help.
That is all. Thank you.
I saw this on Twitter - and no, this can be answered.
First of I have to admit that I am not familiar all too well with EventSource - but from what I see, you are using exactly one script to do two things at once; obtaining the Shopify data and immediately exporting it to JS. However, each PHP script ran has exactly one request and response context - and in this case, you have been echo'ing your data all the way back to Shopify.
You will need two endpoints:
The first endpoint acts as the receipient to Shopify's webhooks (shopify_webhook.php). Each time it receives data, you might store it in a database for fire-and-forget actions like Redis, where you set a possibly low TTL. As far as I know, Redis also has queues - so this would probably be a better approach.
The second endpoint is your event source that keeps streaming incoming queue entries to your requesting JavaScript (i.e. shopify_eventsource.php).
Redis here is just a suggestion - but one way or another, you will have to use an intermediate storage to be able to move the incoming data to another output channel. You can write all this in one PHP file where you validate the incoming request to see if it is a Shopify request (and stream) or if it is your JavaScript code requesting the output. But using two endpoints and thus separating code might make it more readable and adjustable should Shopify change APIs or you your own JavaScript receipient.
TL;DR:
Shopify -> Your endpoint -> intermediate storage.
Intermediate storage -> your endpoint -(stream)> your javascript.

Dynamic page refresh JavaScript/PHP or 'Pusher.com'

I'm looking for a solution to be able to dynamically force a refresh on my web page to all my current viewers. It would be easiest if this was done through php however I'm open to suggestions.
I have done some research and have found a possible solution (pusher.com) however I'm not sure how to execute this. Here is my code below:
JavaScript on my web page added to document.ready jQuery':
var pusher = new Pusher('xxx');
var refreshChannel = pusher.subscribe('refreshing');
refreshChannel.bind('refresh-event', function() {
location.reload(true);
});
Here is my php code from their official documentation:
<?php
require('Pusher.php');
$options = array(
'cluster' => 'eu',
'encrypted' => true
);
$pusher = new Pusher(
'xxx',
'xxx',
'xxx',
$options);
$data['message'] = 'hello world';
$pusher->trigger('refresh-event', 'refresh', $data);
?>
I understand how to send a message like shown above but I don't know how to enable the refresh code. Has anyone used this sdk before and knows how?
Alternatively
Would anyone know any libraries or solutions on how to accomplish this task? Again, all I'm looking to do is refresh my web page dynamically to all my current viewers as and when I need to do it.
You callback function which is the second parameter to refreshChannel.bind(...) will run whenever a 'refresh-event' is received on channel 'refreshing'. The $data in your PHP gets passed to that function as well so you can send information with the event.
Your code to do this is almost spot on but it looks like you are forgetting to set the cluster in your javascript, so aren't successfully connecting. If your app is on the 'eu' cluster you need to say so when you try to connect:
var pusher = new Pusher('xxx', {
cluster: 'eu',
encrypted: true
});
You should have been getting "Could not find app by key xxx. Perhaps you're connecting to the wrong cluster." errors in the javascript console when you try to load the page.
edit: also on closer inspection your channel name and event are the wrong way around in your trigger call. It's
$pusher->trigger('channel-name', 'event', $data);
edit 2: also in your javascript you're calling the channel 'refreshing' and in your php you're calling it 'refresh'. These have to be the same.
Your clients (pages that have the JavaScript code) make a WebSocket connection with pusher.com. You must trigger (= post data) to pusher api server via your php script and pusher will forward your data (event name + parameters) to all clients (connected sockets).
If you have your php code for example in index.php and access it via http it will send data to pusher and he will inform your clients.
Alternative to pusher.com you can create your own php WebSocket server.
Libs:
socketo.me and
socket.io

Real Time Refresh / Update

I have a MVC 3 project that publish in a server.
Scenario
For example I have a function for saving a data from (PC1) to (PC2).
It is possible that the viewing of data(data in jqgrid) in (PC2) is open(open in page) by a user and it will auto refresh or update the page or the jqgrid after the (PC1) save a data?
My jqgrid version is 4.3.3.
Hope you guys understand what I mean in my post. Post feedback if down votes. Thanks.
Any help will be accepted.
You might wonna use ajax to accomplish such a job, please read below
if I understand what you mean, is that you wonna poll a server to realtime updates on either intervals or something else...
option 1
Issue a normal stateless ajax call to the server, then force the server to hold the request for a limited time [to overcome server overhead]
This can also be reffered to as reverse ajax or comet.
Unless you are planning to use websocket technology, I hardly stress that you try this.
if(isset($_GET['finite'])){
#declare time for a session
$_SESSION['typing']=$reduce_browser_overhead=time();
#remember to close the session before entering the loop;
#if u dont close, then the browser will not reload the same website untill the connection is close or satisfied
session_write_close();
function loop(){
#do this to access external variables ===> $reduce_browser_overhead;
global $con,$reduce_browser_overhead;
#explicitly check 2exit
#please do this to release mysql connection since they are in a loop
if($reduce_browser_overhead+4<time()){
echo ' ';ob_flush();flush();
if(connection_aborted()){
#do some work here before you finally exit the connection
exit;
}$reduce_browser_overhead=time();
}
#php prepare statement...
$looper=$con->prepare("SELECT ROW FROM TABLE WHERE ID=SOMETHING AND $_SESSION[typing]=SOME_ROW");
#The statement above willcause the loop to work
#If a table had been update and table has not yet updated, this sql will detemin by the current time
#meaning that if the time[integer] of SOME_ROW is not equal to the time in the session variable,
#then it will let go to the client and then again it will continue looping untill the time in the
#SOME TABLE ROW changes....
$looper->execute();
$looper->store_result();
if($looper->num_rows>0){
sleep(2);
#do some work before looping again
loop();
#you have to explicitly return to this loop to work as expected.
return;
}else{
#send back data to the user or the client listening on the connection
session_start();$_SESSION['typing']=time();session_write_close();
#update the session before finishing the request so that the next time the request comes, the time will be equal to the DBserver time in the row and hence causing the loop again and again => more like a cycle
echo 'After some time the server has received new data which is =>> '.$newdata;
}
}loop();
exit;
//In another file on in the same document as the php / your server file ==> do javascript below
//first issue a normal / stateless ajax request to the target server
$.ajax({
//All optional but url required!
url:'abc.php?var_one=blabla',
cache:true,//whether to cache the requests
timeout:(1000*60)*20,//timeoutthe request
success:function(data){
//if the server successfully completed the request
//do some work here with data returned
},
error:function(){
//if the server return an error
//do more work around
//or call the function again
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Constantly read JSON database

I want to constantly read a JSON-formatted js file so my page shows the changes of that file.
I want some content in my page to change everytime I change the database file within the directory.
My files are:
objectoJSON.js:
var rightFencer;
rightFencer = {"name":"Jorge ANZOLA","nacionality":"VEN","points":10};
var leftFencer;
leftFencer = {"name":"John DOE","nacionality":"USA","points":5};
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<center><p id="rightFencerName"></p><p id="rightFencerPoints"></p> - <p id="leftFencerName"></p> <p id="leftFencerPoints"></p></center>
<script src="objetoJSON.js"></script>
<script>
document.getElementById("rightFencerName").innerHTML = rightFencer.name;
document.getElementById("leftFencerName").innerHTML = leftFencer.name;
document.getElementById("rightFencerPoints").innerHTML = rightFencer.points;
document.getElementById("leftFencerPoints").innerHTML = leftFencer.points;
</script>
</body>
</html>
I thought about putting those two scripts into an infinite while loop so by the time I change the file in the directory, it'd change. But it didn't work.
Also, I thought about using setInterval() to run the scripts every few seconds, but I didn't know how to make it work.
As you can see, I'm a complete noob, so ANY idea would be very appreciated.
Your "objectoJSON.js" is not a JSON file... it's a simple javascript object.
A JSON file would be something like this.
{
"rightFencer":{
"name":"Jorge ANZOLA",
"nacionality":"VEN",
"points":10
},
"leftFencer":{
"name":"John DOE",
"nacionality":"USA",
"points":5
}
}
What you are searching for is
Ajax, Server Sent Events or webSockets
Those update the pagecontent without the need to refresh the page or clicking something.
The following codes shows how to interact with each technology.
They have many advantages and disadvantages... to many to write right now.
ask specific and i can add that to the answer.
All the following examples are pure javascript and so don't need any type of library.They work with almost all new browsers... ios,android,windows also.
All the following examples could be adapted to work with a non properly formatted json file like that you posted. Look at the bottom.
Ajax:
Client asks for data
This updates the client every 30seconds.
function $(a){
return document.getElementById(a)
}
function ajax(a,b,c){ // Url, Callback, just a placeholder
c=new XMLHttpRequest;
c.open('GET',a);
c.onload=b;
c.send()
}
function reloadData(){
ajax('database.js',updateText)
};
function updateText(){
var db=JSON.parse(this.response);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
window.setInterval(reloadData,30000);//30 seconds
/*setinterval is a very bad way to update stuff ,
especially with ajax.. there are many other ways to do that.*/
Ajax does not need any type of server if you read the JS file locally.
Also appendding it... but both examples are time based... and that is not good if you have many users online. WS & SSE allow you to update each user individually depending on the necessity.
SSE:
Server sends data when needed
This uses php to create a Server Sent Events Server
Also this updates the client every 30 seconds, but in this case the server updates the client. Using Ajax the client asks the server to update.
The php file "sse.php"
header('Content-Type: text/event-stream'); // specific sse mimetype
header('Cache-Control: no-cache'); // no cache
while(true) {
if(/*something changes*/){
echo "id: ".time().PHP_EOL;
echo "data: ".$data.PHP_EOL;
echo PHP_EOL;
}
ob_flush(); // clear memory
flush(); // clear memory
sleep(30);// seconds
}
The javascript file
function $(a){
return document.getElementById(a)
}
function updateText(e){
var db=JSON.parse(e.data);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
var sse=new EventSource("sse.php");
sse.onmessage=updateText;
WebSockets:
Server sends data when needed, Client asks for data when needed
webSockets is cool ... comunication is bidirectional. it is fast. but you need something like a nodejs server to be able to handle it properly.
function $(a){
return document.getElementById(a)
}
function updateText(e){
var db=JSON.parse(e.data);
$("rightFencerName").innerHTML=db.rightFencer.name;
$("leftFencerName").innerHTML=db.leftFencer.name;
$("rightFencerPoints").innerHTML=db.rightFencer.points;
$("leftFencerPoints").innerHTML=db.leftFencer.points;
}
var ws=new WebSocket('ws://YOURIP:YOURPORT');
/*ws.onopen=function(){ //those events are also aviable with sse
ws.send('WS open!');//sending data to the server
};
ws.onclose=function(){
console.log('WS closed!');
};*/
ws.onmessage=updateText;
Adapting the js
Ajax..
load the "objectoJSON.js" with ajax and evulate it ... but not using eval(). eval is evil. use new Function()
function updateText(){
document.getElementById("rightFencerName").innerHTML = rightFencer.name;
document.getElementById("leftFencerName").innerHTML = leftFencer.name;
document.getElementById("rightFencerPoints").innerHTML = rightFencer.points;
document.getElementById("leftFencerPoints").innerHTML = leftFencer.points;
}
(new Function(this.response+'\n updateText()'))();
or append the script every 30 seconds or whatever....
I don't write that example as it is the worst approach.
With 30 clients it means that you have to read the file from server evey second.
With SSE or WS you read it once and broadcast it to hundreds of clients.
I suggest to fix your json file.
if you have any other questions ask.
I guess you are working with framework which supports websockets.
You could listen for a change in file using websocket.it may return change in data set like new record or update on any record, or using javascript/ajax call get latest content from server and update your HTML.
https://www.websocket.org/demos.html, see foreign exchange dashboard to see how websockets can be used for constantly updating data.
The way you are doing it now isn't scalable, testable or manageable.
You really don't want to save data on the server using plaintext json files.
If you want a more robust framework for handling your use case, I suggest using web sockets on both the client side and server side (socket.io is a great choice), and using RethinkDB on your server as the DB.

Categories

Resources