Get front row seat and watch the development of micro-isv. We make cool product that solves all your problems...

logo

Friends
         
Comet/AJAX push using PHP or Google App. Engine
2009-10-25

Current methods for implementing server to client push requests AKA 'comet' require a server that supports a low overhead way of holding connections open. This makes adding 'comet' features to an exiting application built on a LAMP stack or Google App engine difficult.

One solution is to move some or all of the application to a framework that does comet things natively. This often requires putting another server in front of the existing server to skim off the comet requests, or some other way of avoiding XSS protections in the browser, like fiddling DNS names. CometD/Bayeux requires you to re-architect your application around a publish-subscribe model, which could potentially be a big task. It also introduces a whole lot of extra code into the application, code that needs debugging and keeping track of. You also need to deploy the cometD server somewhere, which is a pain and might impossible if you are using a shared web host.

I propose a solution that doesn't involve any changes to your existing server stack, and works with Apache shared hosting environments and Google App. Engine.

Demo: a shared counter running on GAE

Yes, you can now do comet in PHP.

There still has to be an external service to wake up the client at the appropriate time, but in this case it presents an interface known as an 'Event Variable'. This service is completely general, and never needs to know anything about your application. It is also trivial to implement, and can be hosted very cheaply.

An Event Variable is simple. It is either fired (set) or not, and you can wait for it to be set. If it is already set when you ask, the call returns immediately. If it is not set then the call blocks until someone calls 'set'. There is no way to 'unset' an event variable. You create them, use them and throw them away when done.

The server http://doorbell.verieda.com provides a REST api for this service. There are three operations provided:

  • To create an event variable, pick a large random number

  • To wait for an event variable to be set, GET /<bignumber>

  • To set an event variable, POST /<bignumber>

To try it out on the command line:

curl http://doorbell.verieda.com/<any number>
# in another window
curl -d '' http://doorbell.verieda.com/<any number>

In order for this to avoid XSS restrictions in the browser, the GET request cannot be made using XMLHttpRequest. The solution is to dynamically create a <script> DOM node, which doesn't have the same restrictions. In order to get control back into the application, the result of the GET request is the piece of javascript 'doorbell(<bignumber>);'. The application will implement the 'doorbell' method to catch the completion.

This is all really simple, here is an example:

C → S

“Get new messages after messageId 123”

S → C

“There are no new messages, wait for event 22482 to be signalled”

C → Doorbell

“GET /doorbell/ 22482”

... request hangs

(some time passes)


C2 → S

“Here is a new message”

S → Doorbell

“POST /doorbell/22482”

Doorbell->C

Completes GET request

Context-type: text/javascript



doorbell(22482);

C → S

Get new messages after messageId 123”

S → C

There is one new message...



Read the source of the demo for more information. It provides a handy abstraction using Mochikit's deferreds called 'doDeferredOnDoorbell' which you use like this:

function getBla() {
    d = loadJSONDoc(“/mysite/getnewmessages...”);
    d.addCallBack(function (result) {
        if [result is 'yes there is new stuff'] {
          // handle it as usual
        } else { 
            // we got a 'wait for an event' signal
            d = new Deferred();
            d.addCallBack(function (result) {
                getBla(); // Try the poll again
            });
            doDeferredOnDoorbell(d, thedoorbellid);
        }
    });
}

So there you go, now any application can support Comet requests. Unlike other solutions, it is shared-nothing, and doesn't require deploying a Stomp MQ server.

The event variable abstraction is also easy to use. Clients poll the server. When the server returns 'nothing', change it to say 'nothing, and don't ask again until this event has been triggered'. When the server gets an update, write it to the database as before, then set the event variables in order to wake the clients up.

Instead of polling in a loop and waiting for a timeout, the clients poll and wait for an event to fire before trying again.