PHP Ajax: live report of the PHP algorithm - php

If I have the next algorithm in the file test.php:
for ($i = 0; $i < 1000; ++$i)
{
//Operations...
ALERT_TO_MAIN_PAGE($i);
}
I call test.php with AJAX form the page main.html
How can I track the progress of $i, with live values?
So main.html will show like this, very time the PHP file has completed one iteration:
Done 0.
Done 1.
Done 2.
...

You can try using HTML5 Server Sent Events to send messages like that to the browser.
Right off the website:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* #param string $id Timestamp/id of this connection.
* #param string $msg Line of text that should be transmitted.
*/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

You can't do it this way, because ajax waits for the operation to complete on the php side.
You should run your php offline (in background) and use ajax to simply ask for state (i.e. read info about progress from session).

Related

PHP Server Sent Events locking up every other request

I implemented a simple endpoint on my PHP server, I am subscribing to it in my Angular application using Azure Event Source to be able to send headers with the request.
Connection it self works and I get the stream back, but once the connection is established every other call locks up. They stay in "Pending" state until I close the connection to the SSE endpoint. I've tried session_write_close(); but no help. Is there something I'm missing? Ideally once the connection is established I would not want it to effect any other calls. Any suggestions are much appreciated.
PHP: Server Side Implementation
public function newSSEEvent()
{
session_write_close();
header("Content-Type: text/event-stream");
header("Cache-Control: no-store");
$counter = rand(1, 10);
while (true) {
echo "event: Date\n";
$curDate = date(DATE_ISO8601);
echo 'data: {"time": "' . $curDate . '"}';
echo "\n\n";
$counter--;
if (!$counter) {
echo "event: Ping\n";
echo 'data: Last Ping' . $curDate . "\n\n";
$counter = rand(1, 10);
}
ob_flush();
flush();
sleep(1);
}
}
Angular: Client Side Implementation
async getEvent(url) {
await fetchEventSource(
url,
{
method: "GET",
headers: {
CustomHeaders
},
onmessage(ev) {
console.log(ev);
},
}
);
}

Run PHP code after ending the HTTP request

I'm writing a simple code to simply show to clients, data that is actually loaded from another HTTP server. The problem is that loading it from the remote server can take up to multiple seconds, and I don't want that much page load delay. So, I make my server cache a copy of this data. So that whenever a client sends a request to my server, it sends the ready-loaded copy and then loads a new copy from the remote server to update the local copy in case any changes were made.
So here's my pseudo code:
if(file_exists($cache_path)){
echo file_get_contents($cache_path);
// I need to end the HTTP request and close the connection here while continuing with the code.
$uptodate_content = file_get_contents("https://docs.google.com/document/export?format=pdf&id=$id");
// I don't want the user to wait for nothing, until this line.
}
else {
$uptodate_content = file_get_contents("https://someremotehost.com/someresource");
echo $uptodate_content;
}
echo file_put_contents($cache_path, $uptodate_content);
Hi I think the best solution is using a queue For example if you use the the queue, you can send it to the queue and then your consumer can pick it from the queue when it has time and user do not need to wait for it
This link is helpful
And this link will help you to use redis for this problem
This is a bad practice.
The connection can never end and you should be careful with such code
The better method is to run a cron job/queue every houerget data from remote server, or alternatively the remote server will trigger a trigger when updating data.
<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort();
ob_start();
//your code
//your code
//your code
echo "response foo bar";
$obSize = ob_get_length();
header("Content-Length: $obSize");
ob_end_flush();
flush();
session_write_close();
// Do processing here
request_to_remote_server();
One way of doing it:
First, create a new PHP file, let's call it update.php, and write the following:
if (isset($argv[1])) {
storeDocumentToCache($argv[1]);
}
And in your current file, change the code to:
echo readDocumentFromCache($id) ?? storeDocumentToCache($id);
In old PHP versions (<7) it should be:
$content = readDocumentFromCache($id);
echo isset($content) ? $content : storeDocumentToCache($id);
Then require the following helper functions in both files (and set $cache_path):
function readDocumentFromCache($id, $fetch = true)
{
$cache_path = "?";
if (file_exists($cache_path)) {
return file_get_contents($cache_path);
}
if ($fetch) {
execInBackground("php " . __DIR__ . "/update.php $id");
}
return null;
}
funciton storeDocumentToCache($id)
{
$cache_path = "?";
$uptodate_content = file_get_contents("https://docs.google.com/document/export?format=pdf&id=$id");
file_put_contents($cache_path, $uptodate_content);
return $uptodate_content;
}
function execInBackground($cmd)
{
if (substr(php_uname(), 0, 7) == "Windows") {
pclose(popen("start /B " . $cmd, "r"));
} else {
exec($cmd . ' > /dev/null 2>/dev/null &');
}
}

How to increase Server-Sent Event's reopen time?

I'm working to show notifications from Server-Sent Event. I checked that every time the browser tries to reconnect with the source about 3 seconds after each connection is closed. That event is getting a call too fast, so my server is loaded too.
So how do I change the reopening time to increase? I have to do at least 60 seconds, so tell me how to do it?
I'm trying the following code.
<table class="table" id="notification"></table>
<script type="text/javascript">
var ssevent = null;
if (!!window.EventSource) {
ssevent = new EventSource('ssevent.php');
ssevent.addEventListener('message', function(e){
if(e.data){
json = JSON.parse(e.data);
if(json){
json.forEach(function(v,i){
html = "<tr><td>"+ v.text +"</td><td>"+ v.date +"</td></tr>";
});
$('#notification').append(html);
}
}
}, false);
ssevent.addEventListener('error', function(e) {
if (e.readyState == EventSource.CLOSED){
console.log("Connection was closed.");
}
}, false);
} else {
console.log('Server-Sent Events not support in your browser');
}
</script>
The file of event stream is as follow.
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
include_once "config.php";
$time = isset($_SESSION['last_event_time']) ? $_SESSION['last_event_time'] : time();
$result = $db->quesry("SELECT * FROM event_noti WHERE event_time < {$time} ")->rows;
$_SESSION['last_event_time'] = time();
if($result){
$json = array();
foreach ($result as $row){
$json[] = array(
'text' => $row['event_text'],
'date' => date("Y-m-d H:i", $row['event_time']),
);
}
echo "data: ". json_encode($json) . "\n\n";
flush();
}
Fundamentally you cannot control this: it is a browser-specific setting.
If your browser is Firefox it appears to be controlled by this setting: dom.server-events.default-reconnection-time with a default of 5000ms.
Taking a step back: the reconnect only happens if the server closes the connection. Why are you closing the connection? (*) Why is a 3-second re-connection too fast?
The point of SSE is to minimize latency; the trade-off is more resource usage, in particular having to keep a dedicated socket open for each client.
So it sounds like you don't want to be using SSE, and instead want to use a simple AJAX poll, on a 60-second setInterval() call?
*: If you did intend to keep it open, you need to wrap your query and processing the result code in a while(true){...} loop. Put e.g. a one-second sleep at the end of the while loop to stop the DB server being overloaded.
Now I have my answer.
Controlling Reconnection-Timeout:
The browser attempts to reconnect the connection to the source within about 3 seconds after each server-sent event connection is closed. Before trying to reconnect, you can change that timeout by starting the line with retry: and then adding the millisecond number to wait.
I changed the code below and started working as I wanted.
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
include_once "config.php";
$time = isset($_SESSION['last_event_time']) ? $_SESSION['last_event_time'] : time();
$result = $db->quesry("SELECT * FROM event_noti WHERE event_time < {$time} ")->rows;
$_SESSION['last_event_time'] = time();
echo "retry: 60000\n"; // 60 seconds, to wait for next connection.
$json = array();
if($result){
foreach ($result as $row){
$json[] = array(
'text' => $row['event_text'],
'date' => date("Y-m-d H:i", $row['event_time']),
);
}
}
echo "data: ". json_encode($json) . "\n\n";
flush();
Source from

Data getting lost in server-sent event with PHP handler

I'm working on a one-way messaging system using server-sent events. I have a file (server.html) which sends the contents of a textarea to a PHP file (handler.php).
function sendSubtitle(val) {
var xhr = new XMLHttpRequest();
var url = "handler.php";
var postdata = "s=" + val;
xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(postdata);
//alert(val);
}
This works (alert(val) displays the text in the textarea).
My handler.php code looks like this:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$stringData = $_POST['s'];
echo "data: Data is {$stringData}\n\n";
flush();
And the relevant part of my SSE receiver file (client.html) is as follows:
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("handler.php");
source.onmessage = function(event) {
var textarea = document.getElementById('subtitles');
textarea.value += event.data + "<br>";
textarea.scrollTop = textarea.scrollHeight;
};
} else {
document.getElementById("subtitles").value = "Server-sent events not supported.";
}
The problem is that client.html only displays "data: Data is", so the text from server.html is getting lost somewhere along the way. I imagine it's the PHP code that's falling over, but I can't work out what's wrong. If anyone can help, I'd appreciate it.
EDIT
I chose to use SSE as opposed to websockets as I only need one-way communication: server.html should push the contents of its textarea to client.html whenever it changes. All the examples of SSE that I've looked at (and I've looked at a lot!) send "automatic" time-based data. I haven't seen any that use real-time user input. So perhaps I should clarify my original question and ask, "How can I use SSE to update a DIV (or whatever) in web page B whenever the user types in a textarea in web page A?"
UPDATE
I've narrowed the issue down to the while loop in the PHP file and have therefore asked a new question: Server-side PHP event page not loading when using while loop
Assuming you want to send a value from server.html and a value at client.html will be automatically updated...
You will need to store the new value somewhere because multiple instances of a script do not share variables just like that. This new value can be stored in a file, database or as a session variable, etc.
Steps:
Send new value to phpScript1 with clientScript1.
Store new value with phpScript1.
Connect clientScript2 to phpScript2.
Send stored value to clientScript2 if it is changed.
Getting the new value 'on the fly' means phpScript2 must loop execution and send a message to clientScript2 whenever the value has been changed by clientScript1.
Of course there are more and different approaches to achieve the same results.
Below there's some code from a scratchpad I've used in previous project.
Most parts come from a class (which is in development) so I had to adopt quite a lot of code. Also I've tried to fit it into your existing code.
Hopefully I didn't introduce any errors.
Do note I did not take any validation of your value into account! Also the code isn't debugged or optimized, so it's not ready for production.
Client side (send new value, e.g. your code):
function sendSubtitle(val) {
var xhr = new XMLHttpRequest();
var url = "handler.php";
var postdata = "s=" + val;
xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(postdata);
//alert(val);
}
Server side (store new value):
<?php
session_start();
$_SESSION['s'] = $_POST['s'];
Client side (get new value):
//Check for SSE support at client side.
if (!!window.EventSource) {
var es = new EventSource("SSE_server.php");
} else {
console.log("SSE is not supported by your client");
//You could fallback on XHR requests.
}
//Define eventhandler for opening connection.
es.addEventListener('open', function(e) {
console.log("Connection opened!");
}, false);
//Define evenhandler for failing SSE request.
es.addEventListener('error', function(event) {
/*
* readyState defines the connection status:
* 0 = CONNECTING: Connecting
* 1 = OPEN: Open
* 2 = CLOSED: Closed
*/
if (es.readyState == EventSource.CLOSED) {
// Connection was closed.
} else {
es.close(); //Close to prevent a reconnection.
console.log("EventSource failed.");
}
});
//Define evenhandler for any response recieved.
es.addEventListener('message', function(event) {
console.log('Response recieved: ' + event.data);
}, false);
// Or define a listener for named event: event1
es.addEventListener('event1', function(event) {
var response = JSON.parse(event.data);
var textarea = document.getElementById("subtitles");
textarea.value += response + "<br>";
textarea.scrollTop = textarea.scrollHeight;
});
Server side (send new value):
<?php
$id = 0;
$event = 'event1';
$oldValue = null;
session_start();
//Validate the clients request headers.
if (headers_sent($file, $line)) {
header("HTTP/1.1 400 Bad Request");
exit('Headers already sent in %s at line %d, cannot send data to client correctly.');
}
if (isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] != 'text/event-stream') {
header("HTTP/1.1 400 Bad Request");
exit('The client does not accept the correct response format.');
}
//Disable time limit
#set_time_limit(0);
//Initialize the output buffer
if(function_exists('apache_setenv')){
#apache_setenv('no-gzip', 1);
}
#ini_set('zlib.output_compression', 0);
#ini_set('implicit_flush', 1);
while (ob_get_level() != 0) {
ob_end_flush();
}
ob_implicit_flush(1);
ob_start();
//Send the proper headers
header('Content-Type: text/event-stream; charset=UTF-8');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // Disables FastCGI Buffering on Nginx
//Record start time
$start = time();
//Keep the script running
while(true){
if((time() - $start) % 300 == 0){
//Send a random message every 300ms to keep the connection alive.
echo ': ' . sha1( mt_rand() ) . "\n\n";
}
//If a new value hasn't been sent yet, set it to default.
session_start();
if (!array_key_exists('s', $_SESSION)) {
$_SESSION['s'] = null;
}
//Check if value has been changed.
if ($oldValue !== $_SESSION['s']) {
//Value is changed
$oldValue = $_SESSION['s'];
echo 'id: ' . $id++ . PHP_EOL; //Id of message
echo 'event: ' . $event . PHP_EOL; //Event Name to trigger the client side eventhandler
echo 'retry: 5000' . PHP_EOL; //Define custom reconnection time. (Default to 3000ms when not specified)
echo 'data: ' . json_encode($_SESSION['s']) . PHP_EOL; //Data to send to client side eventhandler
//Note: When sending html, you might need to encode with flags: JSON_HEX_QUOT | JSON_HEX_TAG
echo PHP_EOL;
//Send Data in the output buffer buffer to client.
#ob_flush();
#flush();
}
//Close session to release the lock
session_write_close();
if ( connection_aborted() ) {
//Connection is aborted at client side.
break;
}
if((time() - $start) > 600) {
//break if the time exceeds the limit of 600ms.
//Client will retry to open the connection and start this script again.
//The limit should be larger than the time needed by the script for a single loop.
break;
}
//Sleep for reducing processor load.
usleep(500000);
}
You called handler.php first time in the server.html and again in client.html. Both are different processes. The variable state won't be retained in the web server. You need to store it somewhere if you want that value in another PHP process. May be you can use sessions or database.
While using sessions you can store the values in two files like:
<?php
//server.php
session_start();
$_SESSION['s'] = $_POST['s'];
And in client.php
<?php
//client.php
session_start();
echo "data: Data is ".$_SESSION['s']."\n\n";

Reading Server Side Events in Python using sseclient

I'm new to Server Side Events and started some tests with PHP on the server side and Python on the client side using the sseclient library.
Using a very basic PHP script, based on the w3schools tutorial I can see the data being received in Python:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$time = date('r');
// echo "data: The server time is: {$time}\n\n";
// flush();
sendMsg(time(),"The server time is: {$time}\n\n");
?>
and in Python:
#!/usr/bin/env python
from sseclient import SSEClient
messages = SSEClient('http://pathto/myscript.php')
for msg in messages:
print msg
As a second step I've tried sending data read from an array stored in the $_SESSION variable. This seems to work when I connect to the SSE stream from javascript in the browser, but it doesn't work and I'm not sure why.
Here's my basic PHP script:
<?php
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
// check for session data
if (isset($_SESSION["data"])){
#as long there are elements in the data array, stream one at a time, clearing the array (FIFO)
while(count($_SESSION["data"]) > 0){
$serverTime = time();
$data = array_shift($_SESSION["data"]);
sendMsg($serverTime,$data);
}
}
?>
and the Python script is the same.
Why isn't the sseclient Python script picking up the events from the above PHP script (while a basic JS one does) ?
The PHP session variable is sent as a cookie; if you view your JavaScript version with Firebug (or equivalent) you should see the cookie being sent to the SSE server script.
So you'll need to set up a session for the Python script, and have it send that in a cookie too.
You could confirm this guess by adding some error handling to your PHP script:
...
if (isset($_SESSION["data"])){
//current code here
}else{
sendMsg(time(), "Error: no session");
}

Categories