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");
}
Related
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);
},
}
);
}
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 &');
}
}
I currently have a simple SSE page for testing. I can currently get the page to log when a user connects and while they are connected; however, when the page closes it seems (eg user disconnects) it doesnt log anything. I am using the function error_log for logging. Also, using NGINX and PHP 7.2. Here is my code:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
ignore_user_abort(true);
$aid = 1;
function sendMsg($id , $msg) {
echo "id: $id" . PHP_EOL;
echo "data: " . json_encode(array('x'=> $msg)) . PHP_EOL;
echo "retry: 0" . PHP_EOL;
echo PHP_EOL;
#ob_end_flush();
flush();
}//END FUNCTION sendMsg
function endPacket() {
echo "0\r\n\r\n";
#ob_end_flush();
flush();
}//END FUNCTION endPacket
$startedAt = time();
error_log("Starting SSE to User: " . $aid);
while (true) {
set_time_limit(15);
if ( connection_status() != 0 ) {
error_log("Ending SSE to User: " . $aid);
endPacket();
die();
}//END IF
error_log("Connected SSE as User: " . $aid);
sendMsg( $startedAt, $aid . ' | ' . connection_status() );
sleep(10);
}//END WHILE
Your code is failing to detect a disconnect because you need to write to the user before you can detect a disconnect
While it looks like your demo code does this, it actually doesn't. This is what your code does now:
detect_connection();
sendMsg();
sleep();
detect_connection();
sendMsg();
<timeout>
The only way in your example to trigger the connection disconnect message is if the connection is closed the first time you call "sendMsg()", what probably is not going to happen as you are not that quick
I'm building a person to person chat, and want person A's page to refresh, loading new messages from Person B when Person B sends them. How would I send a message/data to Person A when Person B sends a message via PHP? I know I can check on Person A's page via Ajax, but constantly running a MySQL query would drastically bring down the server's speed. Any ideas?
EDIT: Using Server Sent Events, here's my script code:
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("update.php?user=<? echo $recip ?>");
source.onmessage = function(event) {
document.write(event.data);
if (event.data=="yes"){
window.location.href="/chat?with=<? echo $recip ?>";
}
};
} else {
document.getElementById('info-text').innerHTML="Hmm... looks like your browser doesn't support auto updating. Please refresh the page to check for new messages." //'
}
And here's my PHP code:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$user=$_GET['user'];
$sql=mysql_query("SELECT * FROM chatmsg WHERE sender='$myusername' AND receiver='$recip' OR sender='$recip' AND receiver='$myusername'");
$newrows=mysql_num_rows($sql);
if ($newrows!=$_SESSION['chat'.$user]) {
echo "data: yes";
flush();
}
else {
echo "data: no";
flush();
The problem is, nothing is happening when there's a new row in MySQL.
I found the solution, everyone. I still used the Server Sent Events, but made some changes and found the error. Here's the final working code:
PHP:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header("Access-Control-Allow-Origin: *");
$user=$_GET['user'];
$sql=mysql_query("SELECT * FROM chatmsg WHERE sender='$myusername' AND receiver='$user' OR sender='$user' AND receiver='$myusername'");
$newrows=mysql_num_rows($sql);
if ($newrows!==$_SESSION['chat'.$user]) {
$msg="yes";
}
else {
$msg="no";
}
echo "data: {$msg}\n\n";
flush();
sleep(10);
(sleep is to save server resources).
JS:
var source = new EventSource('update.php?user=<? echo $recip ?>');
source.onmessage = function(e) {
if (e.data=="yes") {
window.location.href="viewchat.php?viewer=<? echo $viewer ?>&recip=<? echo $recip ?>";
}
}
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).