Reputation: 3387
I want to do real-time monitor for a file on server using Server Sent Event
, below is the javascript and php code
$(function(){
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("./monitor_server_side.php");
var update = 0;
source.onmessage = function(e){
$('#monitor').html(e.data);
update++;
$('#update-time').html("updated " + update + " times");
};
source.onerror = function(e){
console.log(e);
}
}
else {
$('#no-update-warning').text("no update");
}
});
<?php
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
if(!isset($_SESSION['test']))
$_SESSION['test'] = 0;
$_SESSION['test']++;
if(isset($_SESSION['file_path']))
{
$time = date('r');
$file_path = $_SESSION['file_path'];
echo file_get_contents($_SESSION['file_path']);
}
flush();
?>
However, if I modified the file content of $_SESSION['file_path']
, the content shown on web page is not updated.
If I use the below php code given by w3schools.com, every thing goes well
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>
Upvotes: 1
Views: 1936
Reputation: 3496
The reason your own code doesn't work, while the example does, is that you need to start each line with data:
, and end each "transmission" with a double line break (\n\n
).
The following example will read a file every second and send updates to the browser each time the file has changed.
<?php
//Open session
session_start();
//Set the filename if not already set
if (!isset($_SESSION['file_path']) || !is_file($_SESSION['file_path'])) {
$_SESSION['file_path'] = __DIR__ . '/updates.txt';
}
//Store the filename in a local variable
$filename = $_SESSION['file_path'];
//Make sure to close the session
session_write_close();
//Send the required header
header('Content-Type: text/event-stream');
//Prevent caching of the response
header('Cache-Control: no-cache');
//Keep track of the previously read content
$previous_content = '';
//Counts number of file reads (only for testing)
$read_number = 0;
//Set reconnect interval
echo "retry: 5000\n\n";
// Enter an eternal loop
while(true) {
/**
* Try to increase the max_execution_time (note that your provider may
* well prevent this!) Calling it inside each loop will reset the timer,
* allowing it to run forever.
*/
set_time_limit(30);
//Increment read counter (only for testing)
$read_number++;
//Check that the file exists
if(is_file($filename)) {
//Get the files content
$current_content = file_get_contents($filename);
//Compare to previous content to only send updates when needed
if ($current_content !== $previous_content) {
//Reset output
$output = '';
//Append the read counter (only for testing)
$output .= 'data: read number ' . $read_number . "\n";
//Append the file content including "data: " prefix on each line
$output .= 'data: ' . str_replace("\n", "\ndata: ", $current_content) . "\n\n";
//Store the content, so we know not to resend the same
$previous_content = $current_content;
//Send the output
echo $output;
//Uncomment for debugging
//file_put_contents('output.txt', "----" . $output . "----", FILE_APPEND);
//Flush output buffer
ob_flush();
//Flush apache buffer
flush();
}
}
/**
* Wait a bit before next iteration - you may want to reduce this to a
* usleep(100000) or something to get updates faster - keep in mind that
* file_get_contents is probably an expensive operation, so don't reduce
* the delay too much or you will hog lots of resuources
*/
sleep(1);
}
//We'll never get to this point
Your client-side code works as is, though I would recommend adding a little more error-handling to avoid confusing messages in the console log - this is what I used:
<!doctype html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$ = jQuery;
$(function(){
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("./monitor_server_side.php");
var update = 0;
source.onmessage = function(e){
$('#monitor').html(e.data);
update++;
$('#update-time').html("updated " + update + " times");
};
source.onerror = function(e){
switch( e.target.readyState ){
case EventSource.CONNECTING:
console.log('Reconnecting...');
break;
case EventSource.CLOSED:
console.log('Connection failed. Will not retry.');
break;
default:
console.log(e);
break;
}
};
}
else {
$('#no-update-warning').text("No Server Sent Events support");
}
});
</script>
</head>
<body>
<div id="no-update-warning"></div>
<pre id="monitor"></pre>
<div id="update-time"></div>
</body>
</html>
I'll strike the rest of my original answer, as all obstacles have been overcome by the above code - except one: Server Sent Events are not supported in Internet Explorer
SSE now seems like a very attractive alternative to websockets in situations where it is not possible to use websockets (which is the case for most inexpensive shared hosting accounts) - as long as you provide a fallback for browsers that do not support it.
Each client keeps an open connection to the server for the duration of the request (plus in some cases probably another 30 seconds or whatever set_time_limit()
is used) - if this is implemented on an apache server there are several limitations imposed on the number of simultanous connections - which means that in an environment where more than a few clients can be expected this may "kill" the rest of the site or even the server.
It will also take a huge amount of resources (ie. RAM) per client, which could cause its own problems.
These concerns could easily be overlooked in development due to few simultanous clients, but may in many cases wreak havoc as soon as the system is put in production!
In other words: The protocol itself is efficient, but implementation in apache is dangerous territory. It is very possible that other servers (nginx, node.js etc.) do not have this problem, but this is outside my area of expertise. Writing a standalone server could definitely solve the problems - but I can't think of scenarios where that would be possible and using websockets wouldn't.
You should also be aware of browsers implementing a maximum concurrent connections limit - though the problem is not nearly as serious. If you use more than one SSE-connection per client (you really shouldn't) or if users open several tabs then the browsers limit could be reached, and depending on which browser, this could either prevent the SSE-connection from working, or prevent any subsequent requests from completing (ie. new page loads, stylesheets, images and so on).
I have never heard of SSE before this, but this works:
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
if(!isset($_SESSION['test'])) {
$_SESSION['test'] = 0;
}
$_SESSION['test']++;
if (!isset($_SESSION['file_path'])) {
$_SESSION['file_path'] = __DIR__ . '/updates.txt';
}
echo 'data: ';
if(is_file($_SESSION['file_path']))
{
$time = date('r');
$file_path = $_SESSION['file_path'];
echo file_get_contents($_SESSION['file_path']);
}
echo "\n\n";
flush();
First of all I do not see where you set the session variable file_path
- so I set it if not already set.
But the reason you get nothing (even if file_path
is set and the file exists) is that you need to prefix the string with data:
and write out the newline chars (\n\n
) after it.
As far as I can tell this works similar to sending seperate GET
requests - except it is less compatible, since it is not supported in IE
My testing (using firefox) shows one seperate GET
request roughly every 5 seconds - which means you do not get anything in realtime anyway.
If I were you I would simply use ajax-requests instead (ie. $.get([...])
)... Or websockets if that is an option, and you really need real realtime data updates.
Reading a bit further it turns out you can get realtime updates - but you will likely need to work around a lot of obstacles to do it. Firstly you need to keep the php-script alive, in it's simplest form that would mean doing something like this (this will keep the script going for 20 seconds):
for($i=0;$i<20;$i++) {
echo 'data: ';
echo 'read number ' . $i . ' :: ';
if(is_file($_SESSION['file_path'])) {
$time = date('r');
$file_path = $_SESSION['file_path'];
echo file_get_contents($_SESSION['file_path']);
}
echo "\n";
flush();
sleep(1);
}
echo "\n";
...however this won't work an a "standard" apache because of internal output buffering (ie. even though you tell php to flush()
the content, apache will still cache it before actually sending it to the browser). I've had to deal with this in the past, and while the reasoning and discussion of that is way out of scope for this answer - let me just say it is not easy, and it requires some extremely inefficient and ugly hacks (like sending several KBs of useless data). If you are not using apache, but a webserver that provides better control of actual output, or even a custom-webserver where you have full control of the data sent, it may be feasable to use Server Side Events to get live updates in the browser (provided the browser supports it).
So TL;DR: While hearing of this (to me) new method of live updates was intriguing, after some research I'm sure I will never use it, and I still recommend dropping it at an early stage and going with a more mainstream approach.
Upvotes: 2
Reputation: 28913
First, SSE is a wonderfully simple protocol, but you must use the 7 bytes it requires. So prefix your file_get_contents()
call with "data:"
and suffix it with "\n\n"
. As well as flush()
also do ob_flush()
unless you can be 100% sure that PHP output buffering is off.
The other part of the puzzle is that a PHP script runs forever. In the case of a file-monitoring script, you want to wrap it in a while(1){...}
loop, and put some kind of sleep inside that loop.
You need to be aware that PHP sessions are locked. So (because your PHP script is now going to be running forever) you want to read what you need from the session, but then release it by calling session_write_close();
. Make sure you do this before entering the while(1){...}
loop.
As a final refinement, you only want to send data when it has changed, so store the previous string, compare to it, and if the same skip over the output and go straight to the sleep
.
Here is your code with all those fixes done:
<?php
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
if(!isset($_SESSION['test']))
$_SESSION['test'] = 0;
$_SESSION['test']++;
if(!isset($_SESSION['file_path'])){
echo "data:Bad session\n\n";
exit;
}
$file_path = $_SESSION['file_path']
session_write_close();
$prev = '';
while(1)
{
$s = file_get_contents($file_path);
if($s != $prev){
echo "data:{$s}\n\n";
@ob_flush();@flush();
}
sleep(5); //Poll every 5 secs
}
Upvotes: 3
Reputation: 3367
I think the issue is the with the session itself.
When you do a session_start()
PHP loads all the variables into the $_SESSION
variable, considering this is a single connection(no requests are being made past the first one). the session super global is loaded once. if you change it using other files it won't matter, because the session file(on the system) doesn't get read again, thus the $_SESSION superglobal
doesn't change.
You can confirm this by removing the dependency on the session(like w3 did).
Upvotes: 0
Reputation: 329
In order to monitor changes, you can take help of last modified time for a current monitored file.
<?php
// outputs e.g. somefile.txt was last modified: December 29 2002 22:16:23.
$filename = 'somefile.txt';
if (file_exists($filename)) {
echo "$filename was last modified: " . date ("F d Y H:i:s.", filemtime($filename));
}
?>
Upvotes: 0