I'm just starting with PHP and Server Sent Events.
After checking out a couple of articles,like the W3C and HTML5Rocks one I was able to get something off the ground very fast.
What I'm trying to do now is sending a Server Sent Event when my php script receives a POST. Here's what my naive attempt looks like:
<?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();
}
$method = $_SERVER['REQUEST_METHOD'];
if ($method == 'POST') {
$serverTime = time();
$data = file_get_contents("php://input");
sendMsg($serverTime,$data);
}
?>
This doesn't seem to work, but I can't work out why.
I can't see any errors and using JS I can see a client can connect,
but no data comes through when performing a POST action.
What's the recommended way of sending a Server Sent Event when a server receives a POST ?
I'll quote straight from my SSE book, ch.9, the "HTTP POST with SSE" section:
If you bought this book just to learn how to POST variables to an SSE
backend, and you’ve turned straight to this section, I’d like you to
take a deep breath, and make sure you are sitting down. You see, I
have some bad news. ... The SSE standard has no way to allow you to
POST data to the server. This is a very annoying oversight, ...
The alternative is to go back to the pre-SSE alternatives (because XMLHttpRequest, i.e. AJAX, does allow POST); the book does cover that in quite some detail.
Actually, there is one other workaround, that is actually rather easy given that you are using PHP: first post the data to another script, use that to store your POST data in $_SESSION, and then have your SSE script get it out of $_SESSION. (That is not quite as ugly as it sounds: the SSE process is going to be long-running, so one extra http call to set it up is acceptable.)
Related
I have a Wordpress website with a working order system. Now I want to make an Android app which displays every new order in a list view as soon as the order was made.
The last two days I thought about the following solutions:
Simple HTTP GET requests every 10 seconds
Websockets
MySQL binary log + Pusher Link
Server Sent Events
My thoughts (working with a LAMP stack):
Simple HTTP requests are obviously the most ineffective solution.
I figured out that websockets and Apache aren't working well together.
Feels quite hacky and I want to avoid any 3rd party service if I can.
4. Looks like this is the optimal way for me, however there are some problems with Apache/php and Server Sent Events from what I experienced.
I tried to implement a simple demo script but I don't understand why some of them are using an infinite while loop to keep the connection open and others don't.
Here is an example without a loop and here with an infinite loop, also here
In addition to that, when I tested the variant with the infinite loop, my whole page won't load because of that sleep() function. It looks like the whole server freezes whenever I use it.
Does anyone have an idea how to fix that? Or do you have other suggestions?
That is the code that causes trouble (copied from here) and added a missing curly bracket:
<?php
// make session read-only
session_start();
session_write_close();
// disable default disconnect checks
ignore_user_abort(true);
// set headers for stream
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
header("Access-Control-Allow-Origin: *");
// Is this a new stream or an existing one?
$lastEventId = floatval(isset($_SERVER["HTTP_LAST_EVENT_ID"]) ? $_SERVER["HTTP_LAST_EVENT_ID"] : 0);
if ($lastEventId == 0) {
$lastEventId = floatval(isset($_GET["lastEventId"]) ? $_GET["lastEventId"] : 0);
}
echo ":" . str_repeat(" ", 2048) . "\n"; // 2 kB padding for IE
echo "retry: 2000\n";
// start stream
while(true){
if(connection_aborted()){
exit();
}
else{
// here you will want to get the latest event id you have created on the server, but for now we will increment and force an update
$latestEventId = $lastEventId+1;
if($lastEventId < $latestEventId){
echo "id: " . $latestEventId . "\n";
echo "data: Howdy (".$latestEventId.") \n\n";
$lastEventId = $latestEventId;
ob_flush();
flush();
}
else{
// no new data to send
echo ": heartbeat\n\n";
ob_flush();
flush();
}
}
// 2 second sleep then carry on
sleep(2);
}
?>
I'm thankful for every advice I can get! :)
EDIT:
The main idea is to frequently check my MySQL database for new entries and if there is a new order present, format the data nicely and send the information over SSE to my android application.
I already found libraries to receive SSEs on android, the main problem is on the server side.
Based on your question I think you could implement SSE - Server sent events, which is part of HTML5 standard. It is a one-way communication from server to client. It needs html/javascript and a backend language, e.g PHP.
The client will subscribe on events and when subscription is up and running the server will send any updates from the input data. As standard the update will be visible each 3 seconds. This can be adjusted though.
I would recommend you to first create a basic functioning web-browser-client as a start. When and if it is working as you expect, only then you would judge about the effort of building the client as an app.
You would probably need to add functions on the client-side, such as start/stop the subscription.
My understanding of users not recommending the combination of (server sent events) and Apache is the lack of control how many open connections there are and what would control the continuously need of closing of connections. This could lead to sever server performance problems.
Seems using for example node.js would not cause that problem.
Here are some start link:
MDN:
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
Stream Updates with Server-Sent Events:
https://www.html5rocks.com/en/tutorials/eventsource/basics/
I'm having a curiosity issue and don't seem to find the correct phrases for expressing what I mean, for a successful Google search query.
Some sites (that mostly do price queries) do an ajax query to something (let's assume it's php script) with user set criteria and the data doesn't get displayed all at once when the query is finished, but you see some parts being displayed from the response earlier (as I assume they become available earlier) and some later.
I'd imagine the ajax request is done to a php script which in turn queries different sources and returns data as soon as possible, meaning quicker query responses get sent first.
Core question:
How would such mechanism be built that php script can return data
multiple times and ajax script doesn't just wait for A response?
I'm rather sure there's information about this available, but unfortunately have not been able to find out even by what terms to search for it.
EDIT:
I though of a good example being cheap flight ticket booking services, which query different sources and seem to output data as soon as it's available, meaning different offers from different airlines appear at different times.
Hope someone can relieve my curiosity.
Best,
Alari
On client side you need onprogress. See the following example (copied from this answer):
var xhr = new XMLHttpRequest()
xhr.open("GET", "/test/chunked", true)
xhr.onprogress = function () {
console.log("PROGRESS:", xhr.responseText)
}
xhr.send()
xhr.responseText will keep accumulating the response given by the server. The downside here is that xhr.responseText contains an accumulated response. You can use substring on it for getting only the current response.
On the server side, you could do output buffering to chunk the response, e.g. like:
<?php
header( 'Content-type: text/html; charset=utf-8' );
for($i = 0; $i < 100; $i++){
echo "Current Response is {$i} \r\n";
flush();
ob_flush();
// sleep for 2 seconds
sleep(2);
}
I just started using SSE in my PHP pages. I can very easy send new data with this code:
while(true) {
if (isset($_GET["selectedName"]) && $_GET["selectedName"] != "empty") {
echo "data:current timestamp for user ".$_GET["selectedName"]." is ".time().PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
sleep(1);
}
But this means, that I'm sending data every second to the client and I have to run a loop. The string in the example is not much data. But later I want to connect to a database and pull stuff from there.
If I would pull something like a whole article (or message or what ever), I would produce a very big amount of data.
Now my question: How can I tell script to provide new data and send it to the client from another PHP script?
I created a little iOS app which uses a small API to send status updates to the server. I'd like one of the API scripts to tell the web interfaces event source script to send the new data.
And only, when I tell it to do so.
How can I achieve this?
Thanks for help, with kind regards, Julian
Regarding the fact I don't have MUCH stuff to send at once (a state number from 0 to 4 and a little message string), I decided to set a variable and check if anything changed:
static $oldStateString;
while(true) {
$result = mysql_query("SELECT * FROM st_workers");
$currentStateString = "";
while ($user = mysql_fetch_array($result)) {
$currentStateString .= $user["stateIndex"].":--;".$user["state"].":--;".$user["humanReadableName"].":__;";
}
if ($currentStateString != $oldStateString) {
echo "data:".$currentStateString.PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
$oldStateString = $currentStateString;
}
sleep(1);
}
BUT: I noticed that you have to start to event handler a little later, not <body onload="">. This produces errors in most browsers except Safari 6 and the current FireFox. The browser doesn't recognize that it's not loading anymore. So the "loading spinner" does not stop until like 5 minutes or more.
I've recently discovered EventSource, YUI3 has a Gallery module to normalise and fallback behaviour, that's what I've chosen to go with in my example as I use that framework already.
So I've searched about quite a bit, read many blogs, posts and examples, all of which show pretty much the same thing: How to set up basic SSE events. I now have 6 examples of open/message/error/close events firing.
What I don't have (what I'd hoped this link was going to give me) is an example of how to fire SSE events which are more useful to my application, I'm trying one called 'update'.
Here's is my basic test page: http://codefinger.co.nz/public/yui/eventsource/test.php (it might as well be an html file, there's no php code in here yet)
And here's the 'message.php' in the EventSource constructor:
<?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();
}
while(true) {
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
sleep(10);
}
// I was hoping calling this file with a param might allow me to fire an event,
// which it does dutifully, but no browsers register the 'data : update' - though
// I do see the response in Firebug.
if( $_REQUEST['cmd'] ){
sendMsg($serverTime, $_REQUEST['cmd'] );
}
?>
From the live example above, you can see that I've tried to use YUI's io module to send a request, with param, to fire my 'update' event when I click the 'update' button. It seems to work, as you can see in Firebug's Net panel, but my event isn't handled (I realise the script above will run that loop again, I just want to get my event handled in connected browsers, then I'll remove/cleanup).
Am I doing this part wrong? Or is there something more fundamental I'm doing wrong? I'm trying to push events in response to my UI's state changing.
This SO question seemed to come close, #tomfumb commented that his next question was going to be "how to send new events to the client after the initial connection is made - now I see that the PHP just has to never stop executing." But surely I'd only send events as they happen... and not continuously...
there are several issues in your approach:
The server-side code that reads the cmd parameter is unreachable because of the infinite loop that sends event data to the client.
You are trying to send an event from the client to the server. It is in the specification name - Server-Sent Events - the server is the sender and the client is the receiver of events. You have options here:
Use the appropriate specification for the job called Web Sockets which is a two-way communication API
Write the logic that makes the desired type of communication possible
If you choose to stay with the SSE API I see two possible scenarios
Reuse the same Event Source connection and store a pool of connections on the server. When the user sends subsequent XMLHttpRequest with the update command, get the EventSource connection from the pool, that was made by this visitor, and send response with it that specifies your custom event type, the default type is message. It is important to avoid entering in the infinite loop that would make another EventSource connection to the client, but the client does not handle it because he made the request with XMLHttpRequest and not with EventSource.
Make all requests with EventSource. Before making a new EventSource request, close the previous one - you can do this from the client or from the server. On the server check the parameters and then send data to client.
Also you can use XMLHttpRequest with (long) polling and thus avoiding the need of using EventSource. Because of the simplicity of your example I can't see a reason to mix the two type of requests.
Do I need to pass back any HTTP headers to tell the browser that my server won't be immediately closing the connection and to display as the HTML is received? Is there anything necessary to get the HTML to incrementally display like flush()?
This technique used to be used for things like chat, but I'm thinking about using it for a COMET type application.
Long polling is a common technique to do something like this; to briefly summarise, it works as follows:
The client sends an XHR to the server.
If there is data ready, the server returns this immediately.
If not, the server keeps the connection open until data does become available, then it returns this.
If the request times-out, go back to 1).
The page running on the client receives this data, and does what it does with it.
Go back to 1)
This is how Facebook implements its chat feature.
This article also clears up some of the misconceptions of long-polling, and details some of the benefits of doing so.
The client will close the connection when it does not receive any data for a certain time. This timeout cannot be influenced by HTTP headers. It is client-specific and usually set to 120 seconds IIRC.
So all you have to do is send small amounts of data regularly to avoid hitting the timeout.
I think a more robust solution is a page with a Javascript timer that polls the server for new data. Keeping the response open is not something the HTTP protocol was designed for.
I would just echo / print the HTML as I went. There are a few different ways you can have the script pause before sending the next bit. You shouldn't need to do anything with headers or any special code to tell the browser to wait. As long as your script is still running it will render the HTML it receives from the script.
echo "<HTML><HEAD.../HEAD><BODY>";
while (running)
{
echo "printing html... </br>";
}
echo "</BODY></HTML>"; //all done
Try forever frame (like in gmail)
All of these technics are just hacks, http isn't designed to do this.
at the end of your script, use something like this (assuming you had output buffering on by putting ob_start() at the top of your page
<?php
set_time_limit(0); // Stop PHP from closing script after 30 seconds
ob_start();
echo str_pad('', 1024 * 1024, 'x'); // Dummy 1 megabyte string
$buffer = ob_get_clean();
while (isset($buffer[0])) {
$send = substr($buffer, 0, 1024 * 30); // Get 30kbs bytes from buffer :D
$buffer = substr($buffer, 1024 * 30); // Shorten buffer
echo $send; // Send buffer
echo '<br />'; // forces browser to reload contents some how :P
ob_flush(); // Flush output to browser
flush();
sleep(1); // Sleep for 1 second
}
?>
That script basically outputs 1 megabyte of text at 30kbs (simulated) no matter how fast the user and server connection is.
Depending on what you are doing, you could just echo as your script proceeds, this will then send the html to the browser as it is echoed.
I would suggest you investigate implementing such functionality using Ajax, rather than plain old HTML. This allows you much more flexibility in terms of architectural design and user interface