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.
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/
in certain apps, you sometimes need to do processing that is irrelevant to response. for example send push notifications after chat message etc. such tasks has no effect on response you will return to user.
what is best approach to run such tasks ?
example in an API for a blog, after post is created i want to send 201 to client and end connection. yet afterwords i want to send a curl call to push notification server, or trigger some data analysis and save it to disk. yet i dont want user to wait for such tasks to end.
methods i can think of
1. is sending connect: closed and content-length headers and flush out response, but this is not compatible with all servers and not all browsers.
2. trigger task using php exec function ! ? but how can i pass a json object to that function then :/ ?
so any ideas how we can accomplish this in async behaviour for php in a manner that would works in any server setup ?
You can take an example of how WordPress triggering wp-cron.php functionality by sending HEAD request to wp-cron.php using curl, which is perfectly fitted to your idea of sending the request and not waiting to respond.
I would do it with this code:
Register as many functions as required with register_shutdown_function of php:
register_shutdown_function('background_function_name_1');
register_shutdown_function('background_function_name_2');
write below lines after html end tag(if any) where all output has been printed (adjust time limit as per upper limit of script execution):
ignore_user_abort(true);
set_time_limit(120);
header('Connection: close');
header('Content-Length: ' . ob_get_length());
ob_end_flush();
flush();
Here the server will send output to browser and all the registered functions will be called in the order they were registered.
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.)
First of all, sorry for my english, I wrote this with some help of Google Translate.
I'm trying to make an application like Google Wave with PHP and Ajax. I have a textarea that when the user input something, the javascript on the page detected with oninput and send the contents of the textarea to the server and the server stores the contents into the database.
What I'm doing is that every time when i send the content by XHR, there is XHR.abort() that always interrupts the previous XHR request. The data that is in the database are fine, however, sometimes it is stored a previous version.
I know it happens because PHP has not stopped the execution even though the client has made an abort and sometimes the previous request has taken more time that the last request and completed after the last request, so I read the manual of functions of "ignore_user_abort" and "connection_aborted", but the problem persist.
I created this script to simulate the situation and I hoped when I aborted the connection (press 'stop', close the tab/window), there are not any new data on the database, but after 5 seconds, there still I have new data, so I need help to rollback the transaction when user abort the connection.
Here is the script to simulate (PDO_DSN, PDO_USER, PDO_PASS are defined):
<?php
ignore_user_abort(true);
ob_start('ob_gzhandler');
$PDO = new PDO(PDO_DSN, PDO_USER, PDO_PASS, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
$PDO->beginTransaction();
$query = $PDO->query('INSERT INTO `table` (`content`) VALUES (' . $PDO->quote('test') . ')');
sleep(5);
echo ' ';
ob_flush();
flush();
if (connection_aborted()) {
$PDO->rollBack();
exit;
}
$PDO->commit();
ob_end_flush();
If you are finding XHR.abort() and connection_aborted() unreliable, consider other ways to send an out-of-band signal to inform the running PHP request that it should not commit the transaction.
Are you running APC (or could you be)?
Instead of invoking XHR.abort(), you could send another XHR request signaling the abort. The purpose of this request would be to record a special key in the APC user cache. This key's presence would indicate to the running PHP request that it should roll back.
To make this work, each XHR request would need to carry a (relatively) unique transaction identifier, e.g. as a form variable. This identifier would be generated randomly, or based on the current time, and would be sent in the initial XHR as well as the "abort" XHR and would allow the abort request to be correlated to the running request. In the below example, the transaction identifier is in form variable t.
Example "abort" XHR handler:
<?php
$uniqueTransactionId = $_REQUEST['t'];
$abortApcKey = 'abortTrans_' . $uniqueTransactionId;
apc_store($uniqueTransactionId, 1, 15);
Example revised database write XHR handler:
<?php
$PDO = new PDO(PDO_DSN, PDO_USER, PDO_PASS,
array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
$PDO->beginTransaction();
$query = $PDO->query('INSERT INTO `table` (`content`) VALUES (' . $PDO->quote('test') . ')');
$uniqueTransactionId = $_REQUEST['t'];
$abortApcKey = 'abortTrans_' . $uniqueTransactionId;
if (apc_exists($abortApcKey)) {
$PDO->rollBack();
exit;
}
$PDO->commit();
You may still have timing issues. The abort may still arrive too late to stop the commit. To deal with this gracefully, you could modify the database write handler to record an APC key indicating that the transaction had committed. The abort handler could then check for this key's existence, and send back a meaningful XHR abort result to advise the client, "sorry, I was too late."
Keep in mind, if your application is hosted on multiple live servers, you will want to use a shared cache such as memcached or redis, since APC's cache is only shared across processes on a single machine.
How about having the browser send back a timestamp or a running number that you also store in the database. and your update can check so that it only writes if the new timestamp is newer.
I have seen this issue many times with Javascript and Ajax. If you are not very careful in implementing the UI then the user can click twice and/or the browser can trigger the ajax call twice resulting in the same record hitting your database twice. These might be two completely different http requests from your UI, so you need to make sure that your server side code can filter out the duplicates before inserting them into the database.
My solution is usually to query the records entered by the same user recently and check whether this is really a new entry or not. If this new record is not in the database yet then insert it, otherwise ignore.
In Oracle you can use PL/SQL to have a MERGE IF MATCH THEN INSERT command, so you can handle this in one query, but in MySQL you are better off by using two queries - one to query the existing records of this user and then the other one to insert if there is no match.
As Marc B pointed, PHP can't detect if the browser is disconnected without sending some output to the browser. The problem is, you have enabled output buffering at the line ob_start('ob_gzhandler'); and that prevents PHP from sending output to the browser.
You either have to remove that line or add a ob_end_* (for example: ob_end_flush()) along with the echo/flush calls.
Scenario is as follows:
Call to a specified URL including the Id of a known SearchDefinition should create a new Search record in a db and return the new Search.Id.
Before returning the Id, I need to spawn a new process / start async execution of a PHP file which takes in the new Search.Id and does the searching.
The UI then polls a 3rd PHP script to get status of the search (2nd script keeps updating search record in the Db).
This gives me a problem around spawning the 2nd PHP script in an async manner.
I'm going to be running this on a 3rd party server so have little control over permissions. As such, I'd prefer to avoid a cron job/similar polling for new Search records (and I don't really like polling if I can avoid it). I'm not a great fan of having to use a web server for work which is not web-related but to avoid permissions issues it may be required.
This seems to leave me 2 options:
Calling the 1st script returns the Id and closes the connection but continues executing and actually does the search (ie stick script 2 at the end of script 1 but close response at the append point)
Launch a second PHP script in an asynchronous manner.
I'm not sure how either of the above could be accomplished. The first still feels nasty.
If it's necessary to use CURL or similar to fake a web call, I'll do it but I was hoping for some kind of convenient multi-threading approach where I simply spawn a new thread and point it at the appropriate function and permissions would be inherited from the caller (ie web server user).
I'd rather use option 1. This would also keep related functionality closer to each other.
Here is a hint how to send something to user and then close the connection and continue executing:
(by tom ********* at gmail dot com, source: http://www.php.net/manual/en/features.connection-handling.php#93441)
<?php
ob_end_clean();
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
ignore_user_abort(true); // optional
ob_start();
echo ('Text user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // Strange behaviour, will not work
flush(); // Unless both are called !
ob_end_clean();
//do processing here
sleep(5);
echo('Text user will never see');
//do some processing
?>
swoole: asynchronous & concurrent extension.
https://github.com/matyhtf/swoole
event-driven
full asynchronous non-blocking
multi-thread reactor
multi-process worker
millisecond timer
async MySQL
async task
async read/write file system
async dns lookup