I am trying to update a specific element in my HTML document using server-sent events. I am using the W3Schools example with parameters changed to fit my needs. When this did not work out of the box, I tried this question's solution, which is to include charset=utf-8 in my Content-Type header. Still no updates to the HTML. I then realized I was getting the following error:
EventSource's response has a MIME type ("text/html") that is not "text/event-stream". Aborting the connection.
Here is the relevant JavaScript in my .js file:
var source = new EventSource("warehouse.php");
source.onmessage = function(event) {
document.getElementById("processing").innerHTML += event.data + "<br>";
};
In my PHP file, I have the following:
if (isset($args->products)) {
orderProducts($args->products);
}
function orderProducts($products) {
$bigResponse = outgoingData(constant('bigURL'), $products[0]);
$littleResponse = outgoingData(constant('lilURL'), $products[3]);
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache');
echo "data: Order has been placed.";
flush();
}
I am completely stumped. What am I missing? How do I fix this error?
Try sending a json encoded data part/element back to the browser.
Lets see if I can remember correctly... It's out of my mind, so it isn't tested!
PHP Side:
// Set SSE headers which are send along first output.
header('Content-Type: text/event-stream; charset=UTF-8');
header('Cache-Control: no-cache');
$sseId = 0; //Increment this value on each next message
$event = 'eventName'; //Name of the event which triggers same named event-handler in js.
//$data needs to be json encoded. It can be a value of any type.
$data = array ('status' => 'Progress', 'message' => 'My message or html');
//Send SSE Message to browser
echo 'id: ' . $sseId++ . PHP_EOL; //Id of message
if (!is_null($event)) {
echo 'event: ' . $event . PHP_EOL; //Event Name to trigger eventhandler
}
retry: 10000 . PHP_EOL; //Define custom reconnection time. (Default to 3s when not specified)
echo 'data: ' . json_encode($data) . PHP_EOL; //Data to send to eventhandler
//Note: When sending html, you might need to encode with flags: JSON_HEX_QUOT | JSON_HEX_TAG
echo PHP_EOL;
ob_flush();
flush();
JavaScript side:
var es = new EventSource('sse.php');
es.addEventListener('eventName', function(event) {
var returnData = JSON.parse(event.data);
var myElement = document.getElementById("processing");
switch (returnData.status) {
case 'Error':
es.close();
myElement.innerHTML = 'An error occured!';
break;
case 'Done':
es.close();
myElement.innerHTML = 'Finished!';
break;
case 'Progress':
myElement.innerHTML += returnData.message + "<br>";
break;
}
});
es.onerror = function() {
console.log("EventSource failed.");
};
In this case I make use of 1 (named) event only where the data element contains a status on which js reacts.
You can also define multiple event-names to be used as a status and define corresponding event-handlers or define a handler which is triggered on each message received.
es.onmessage = function(event) {
//This handler fires on each message received.
console.log("Incoming message.");
};
If you have server-side script running longer then 3s, specify a longer retry time in the message sent (See code at PHP side). Otherwise the browser will reconnect after 3 seconds which might run your script from the beginning and abandon the previous run.
Related
I'm trying to get to grips with SSE with the help of https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events.
Although everything works fine, I get an error on every server event. The error object doesn't contain the actual error (or I can't find it) but I'm not comfortable ignoring the error.
This is my client code (js):
const evtSource = new EventSource("http://ssedemo/server/ssedemo.php");
evtSource.addEventListener("ping", function(event) {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.innerHTML = "message: " + event.data;
eventList.appendChild(newElement);
});
evtSource.onerror = function(err) {
console.error("EventSource failed:", err);
};
And serverside (php) - abbreviated code from the example in the link
date_default_timezone_set("Europe/Amsterdam");
header("Cache-Control: no-cache");
header("Content-Type: text/event-stream");
$curDate = date(DATE_ISO8601);
echo "event: ping\n";
echo 'data: This is a Ping at time ' . $curDate . "\n\n";
ob_end_flush();
flush();
I see the events, everything works but in the console I get this error:
There's loads of info in there but I can't find the actual error.
Where should I look for the error? Am I missing something?
I am trying to use server sent event to get information from the database and update on my html page, immediately a new message enters, these are my codes
function update()
{
if(typeof(EventSource) !="undefined")
{
var source=new EventSource("update.php");
source.onmessage=function(event)
{
$scope.my_messages=JSON.parse(event.data)
}
}
}
update();
<div ng-repeat="x in my_messages">
<div>{{x.sender_username}}</div>
<div>{{x.title}}</div>
<p>{{x.messages}}</p>
</div>
at the update.php,I have this
<?php
include 'first.php';
$newUser= new Participant();
$newUser->connect();
$newUser->call_new_message();
?>
At first.php, I have this
public function call_new_message()
{
$sender=$_SESSION['username'];
$receiver=$_SESSION['receiverUsername'];
$a = mysqli_query($this->con, "SELECT * from messages_tb WHERE sender_username= '$sender' and receiver_username='$receiver' ");
if ($a) {
$s=$a->fetch_all(MYSQLI_ASSOC);
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo"data:".json_encode($s)."\n\n";
flush();
// echo json_encode($s);
}
else{
echo "An error occurred";
}
}
It worked well except that I expected it to update data in the divs in the html page immediately a new message comes in the messages_tb but it's not working like that, I have to refresh the page before I can get the new message in the div. Please, help out. thanks
I guess adding $scope.$apply() in on scope().message handler should work for you.
source.onmessage=function(event)
{
$scope.my_messages=JSON.parse(event.data);
$scope.$apply();
}
Reason is, angular might not be aware of plain java script updating the data. so forcefully you can tell angular to update the scope.
How can I achieve server sent events in WordPress. I have searched a lot about it but not able to figure out how can I implement it. The problem I am facing is SSE only takes header as 'Content-Type:text/plain'. How can I sent some event to js script only when some changes occur in my database through WordPress function?
If I use 'Content-Type:text/plain' in WordPress function whole website will turn into text output.
I am aware that Wordpress has heartbeat API but I don't want that. Currently, I am using ajax pooling to achieve the same task but it is taking a lot of resources.
you can make a custom REST API end point and make something like that:
public function prepare_rest_api_demo_response( $request ){
$args = array(
'numberposts' => 1
);
$last_id = '';
$serverTime = time();
$latest_posts = get_posts( $args );
$last_id = $latest_posts['0']->ID;
do {
$latest_posts = get_posts( $args );
if( $latest_posts['0']->ID > $last_id ){
$response = new WP_REST_Response($this->compile_sendMsg_in_apiendpoint_for_SSE($serverTime,$latest_posts,$last_id), 200);
break;
}
// break the loop if the client aborted the connection (closed the page)
if ( connection_aborted() ) {
$response = new WP_REST_Response('data: {connection: "closed"}'."\n\n", 503);
break;
}
sleep(2);
// If we didn't use a while loop, the browser would essentially do polling
// every ~3seconds. Using the while, we keep the connection open and only make
// one request.
} while(true);
$response->header( 'Content-Type', 'text/event-stream' );
$response->header( 'Cache-Control', 'no-cache' );
//$response->header( 'Access-Control-Allow-Origin', '*' );
$response->header( 'Accept', 'application/json' );
return $response;
}
and this function for compile the response in the correct way
private function compile_sendMsg_in_apiendpoint_for_SSE($id, $msg, $lastitemid) {
$msg = json_encode($msg);
echo "id: $id" . "\n";
echo "retry: 1500" . "\n";
//echo "event: nameofevent" . "\n"; // if you need some name for event uncomment that line
echo "data: $msg". PHP_EOL;//use \n instead of PHP_EOL for add another line data: if is the same data object https://www.html5rocks.com/en/tutorials/eventsource/basics/#toc-js-api
echo PHP_EOL;
ob_flush();
flush();
}
I am trying to crawl a full section of a website, but the problem is that the data that I need is not there from the start. Is there anyway to get the data from the website with PHP?
this is the link: https://www.iamsterdam.com/nl/uit-in-amsterdam/uit/agenda and this is the section I need:
After my post was set to duplicate I tried this
https://stackoverflow.com/a/28506533/7007968 but is also doesn't work so I need a other solucion this is what I tried:
get-website.php
$phantom_script= 'get-website.js';
$response = exec ('phantomjs ' . $phantom_script);
echo $response;
get-website.js
var webPage = require('webpage');
var page = webPage.create();
page.open('https://www.iamsterdam.com/nl/uit-in-amsterdam/uit', function(status) {
console.log(page.content);
phantom.exit();
});
this is all I get back (around 3% of the page):
</div><div id="ads"></div><script src="https://analytics.twitter.com/i/adsct?p_id=Twitter&p_user_id=0&txn_id=nvk6a&events=%5B%5B%22pageview%22%2Cnull%5D%5D&tw_sale_amount=0&tw_order_quantity=0&tw_iframe_status=0&tpx_cb=twttr.conversion.loadPixels" type="text/javascript"></script></body></html>
So I have the feeling that i am getting closer this is what I after a lot of searching:
var webPage = require('webpage');
var page = webPage.create();
var settings = {
operation: "POST",
encoding: "utf8",
headers: {
"Content-Type": "application/json"
},
data: JSON.stringify({
DateFilter: 04112016,
LastMinuteTickets: 0,
PageId: "3418a37d-b907-4c80-9d67-9fec68d96568",
Skip: 0,
Take: 12,
ViewMode: 1
})
};
page.open('https://www.iamsterdam.com/api/AgendaApi/', settings, function(status) {
console.log(page.content);
phantom.exit();
});
But what I get back doesn't look good:
Message":"An error has occurred.","ExceptionMessage":"Page could not be found","ExceptionType":"System.ApplicationException","StackTrace":" at Axendo.SC.AM.Iamsterdam.Controllers.Api.AgendaApiController.GetResultsInternal(RequestModel requestModel)\r\n at lambda_method(Closure , Object , Object[] )\r\n
etc.
I hope somewann can help me,
Addressing your main question about 3%.
You use exec incorrectly. When used like this
$response = exec ('phantomjs ' . $phantom_script);
$response will containt the last line of what was printed in terminal during execution of a given command. Because you did console.log(page.contents); the last line of HTML document was placed into $response variable.
The correct use of exec would be
exec ('phantomjs ' . $phantom_script, $response);
This way the result will be placed into $response variable as an array, with each line an element of the array. Then, if you just want to get html, you can do
$html = implode("\n", $response);
But a more simple and correct way is to use the specific function for the task:
passthru ('phantomjs ' . $phantom_script);
passthru executes a function and returns recieved data unmodified, straight to the output.
So if you want to contain it to a variable, do:
ob_start();
passthru ('phantomjs ' . $phantom_script);
$html = ob_get_clean();
I am using HTML5 Server-Sent Events.
Actually I need to show notification (new record enter and which are unread) that's when any new record is insert in database (php/mysql).
So for testing purpose I just tried with count of total row. But I am getting this error message in my local-host:
Firefox can't establish a connection to the server at http://localhost/project/folder/servevent/demo_sse.php.
The line is:
var source = new EventSource("demo_sse.php");
I have tried this:
index.php
<script>
if(typeof(EventSource) !== "undefined") {
var source = new EventSource("demo_sse.php");
source.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data;
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
<div id="result"></div>
demo_sse.php
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$db = mysql_connect("localhost", "root", ""); // your host, user, password
if(!$db) { echo mysql_error(); }
$select_db = mysql_select_db("testdatase"); // database name
if(!$select_db) { echo mysql_error(); }
$time = " SELECT count( id ) AS ct FROM `product` ";
$result = mysql_query($time);
$resa = mysql_fetch_assoc($result);
echo $resa['ct'];
flush();
?>
Please let me know what going wrong.
I know for notification we can use Ajax with some interval time, but I don't want such thing. As I have N number of records and which may slow my resources.
According to this,
There are several 'rules' that need to be met, and yours is lacking at this point:
Output the data to send (Always start with "data: ")
It is somehow like:
echo "data: {$resa['ct']}\n\n";
Setting a header to text/event-stream worked for me:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// Rest of PHP code
Please modify the following code snippet according to your requirements
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// infinite loop
while (1) {
// output the current timestamp; REPLACE WITH YOUR FUNCTIONALITY
$time = date('r');
echo "data: Server time: {$time}\n\n"; // 2 new line characters
ob_end_flush();
flush();
sleep(2); // wait for 2 seconds
}
?>
I tested this code snippet myself; it's working for me. If you have any query, let me know.