Long poll jsonp
UPDATE: Implemented changes to the code as suggested by Akeru, thanks!
UPDATE2: Updated to the newer version of jQuery-jsonp, thanks Julian, the Ugly spinner loading is now gone!
Go straight to the code
or
Download it in a zip file
So, whatever happened to comet? Wasn’t it supposed to save the masses from wasteful ajax polling? I certainly appreciate Alex Russel’s work on the matter, but it really seems as though this very useful technology is falling to the wayside. I think this is mainly due to the difficulty and requirements of a successful implementation on the most popular web server, the main problem is Apache’s use of threading, this post explains the problem eloquently. Of course Apache have a whole project that deals with this issue to a degree, but I don’t think that it is easy to implement, once you dig a little into the documentation, you quickly see that this is not really a thing that just works out of the box… You need to read a book on the subject first. That is a bit of a deal-breaker for me; after all, the technique is not that difficult to understand, so why should you need to read a whole book to implement it?
So is comet dead?
No, not at all! As a matter of fact, cometd implements the bayeux protocol, and the technology is very much alive; I’m sure that it is very useful, for those who have the time, resources and patience to implement it, but where does this leave the rest of us? You know, the guys who want a quick way to get up and running with some instantly useful code that they can understand and tinker with? Well, comet-d does implement something they call “callback-polling”, and other people seem to call “script tag long polling”, though I prefer the term “long poll jsonp”.
Right, so now what?
Let us look at what exactly “long poll jsonp” is. I’m assuming you already know what jsonp is, so that just leaves “long polling”. To describe long polling, let us look at the backend and front-end parts in general terms.
Back end
On the back-end we simply have a program that doesn’t finish till it has some data for us. That’s all there is to it.
Front end
On the front end we make a jsonp request, which when finished, starts up again. We are of course passing a token that indicates what data we received, for example a timestamp.
That describes long polling in a nutshell; of course there are some pitfalls and issues to watch out for with this technique, such as error handling, (server timeout), and connection timing.
Show me the money code!
It is time to look at some code! Most comet example implement a simple chat client, so I’ll do the same here. First lets looks at the backend, as it is very simple.
jQuery Long Poll plugin 0.0.1 (02-05-2010)
Note: requires jQuery-JSONP: http://code.google.com/p/jquery-jsonp/
Copyright (c) 2010 JavaScript Guy
This script is licensed as free software under the terms of the
LGPL License: http://www.opensource.org/licenses/lgpl-license.php
*/
(function($){
$.longPoll = function( args ) {
args = args || {};
var self = $.extend( {
url: ”, // URL To connect to
tsParameter: ‘ts’, // Parameter used for the timestamp
reconnect: true, // If we’re automatically reconnecting
errorTimeout: 60, // Timeout for errors, eg: server timeout, execution time limit, etc.
errorTimeoutDelay: 5, // Seconds to delay, if the connection failed in an error
hasConnection: false, // If we have a connection
timestamp: 0,
error: false,
request: null,
reconnectFunc: function() {
if( self.reconnect ) {
self.disconnect();
// Reconnect – with longer error timeout delay (in case we have an intermittent connection)
if( self.error ) {
setTimeout( function() { self.connect(); }, self.errorTimeoutDelay * 1000 );
} else {
setTimeout( function() { self.connect(); }, 100 );
}
}
},
connect: function() {
self.reconnect = true;
// self.request = $.ajax( {
self.request = $.jsonp( {
url: self.url + ‘?’ + self.tsParameter + ‘=’ + self.timestamp + ‘&callback=?’,
timeout: self.errorTimeout * 1000, // Timeout and reconnect
error: function(XHR, textStatus, errorThrown) {
self.reconnectFunc();
self.error = true;
},
dataType: "jsonp",
success: function( data ) {
self.timestamp = (data[self.tsParameter])? data[self.tsParameter]: self.timestamp;
self.success( data );
self.error = false;
},
complete: function( data ) {
self.reconnectFunc();
}
} );
self.hasConnection = true;
},
// Aborts request
disconnect: function() {
self.reconnect = false;
if( self.request )self.request.abort();
self.hasConnection = false;
},
// User defined
success: function( data ) {}
}, args );
return self;
};
})(jQuery);
So in the above plugin code, we create a longpoll object, and then setup a bunch of parameters and functions. The reconnectFunc is used to handle reconnection on error or completion of calls; the connect function uses the jQuery-JSONP library to connect to our url. The reason we’re using the jQuery-JSONP library is that natively, (as of version 1.4.2), jQuery’s support for jsonp is flawed. First of all it calls the success method twice (see: http://dev.jquery.com/ticket/6451), and secondly, it doesn’t support abort() correctly, which is a required function for our code to work. I’m sure that this will be fixed in the next version of jQuery.
So, as you can see in the code, we’re handling connection timeouts and general connection errors gracefully; of course I recognise that the comet-d library with the implementation of the bayeux protocol does this too, but on a much grander scale; my library isn’t intended to replace comet-d, it is simply here to provide an alternative implementation of long poll jsonp, as that is the most useful part of comet, to most people.
Let us look at the rest of the chat app, here is my HTML code:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JSONP Long Poll chat demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="jquery-1.4.2.js"></script>
<script type="text/javascript" src="jquery-jsonp-2.0.2.js"></script>
<script type="text/javascript" src="jquery-longpoll-0.0.1.js"></script>
</head>
<body>
<div id="content"></div>
<p>
<input type="button" name="connect" id="connect" value="Connect"/>
</p>
<p>
<input type="text" name="message" id="message" value="" />
<input type="button" name="sendMsg" id="sendMsg" value="Send"/>
</p>
<script type="text/javascript">
// Chat app
var chatApp = function( url ) {
var self = {
connection: $.longPoll( {
url: url,
success: function( data ) {
// Ignore server timeouts, the script will handle it.
if( data['data'].indexOf( "Maximum execution time of" ) == -1 ) {
jQuery(‘#content’).append( ‘<div>’ + data['data'] + ‘</div>’ );
}
}
} ),
// Sends a chat message
sendChat: function(request) {
jQuery.ajax( {
url: url,
type: "GET",
dataType: "jsonp",
data: { ‘msg’ : request }
} );
},
// Toggle connection button
toggleConnect: function( button ) {
if( self.connection.hasConnection ) {
self.connection.disconnect();
jQuery( button ).val("Connect");
} else {
self.connection.connect();
jQuery( button ).val("Disconnect");
}
}
};
return self;
};
</script>
<script>
// Chat app initialisation
var c = chatApp( ‘http://paxjs.com/labs/longpolljsonp/chat.php’ );
var sendMessage = function() {
chatAppContext.sendChat( jQuery(‘#message’).val() );
jQuery(‘#message’).val(”);
};
jQuery( ‘#connect’ ).click( function() {
chatAppContext.toggleConnect( jQuery( this ).get(0) );
} );
jQuery( ‘#sendMsg’ ).click( sendMessage );
jQuery( ‘#message’).keyup(function(e) {
if(e.keyCode == 13)sendMessage();
});
</script>
</body>
</html>
So as you can see, we simply create an input field for the message, and a button to send the message; we then hook up some simple code to each part of the functionality.
Ugly spinner loading in some browsers
Of course this is just a technique, it isn’t perfect – in some browsers, the loading indicator continually shows that a page is loading; a future enhancement will probably fix that… probably.
Updated to the newer version of jQuery-jsonp, (thanks Julian!), the Ugly spinner loading is now gone!
There is a demo here, this is all served off the paxjs domain, to emphasise the cross-site nature of the technique:
9 Responses to “Long poll jsonp”
Leave a Reply

Akeru on May 13th, 2010
Nice article ! Could you provide a zip file with the JS and PHP code to download, so I could play a bit with the concept ?
admin on May 14th, 2010
You bet, uploaded it here, let me know if you have problems, or any good ideas!
Akeru on May 14th, 2010
Thanks ! I played a bit with it today. Had no problem running the demo but ran into some issues.
The first one is that the mtime is not accurate enough (2secs) so I sometimes got 3,4 times the same message until I changed the usleep to a longer value.
By playing a bit with the ts parameter before is was received (using TamperData, or such) I was able to create an infinite loop of refresh, so the ts field might not be a good idea. Use maybe session for that.
I also think it is a better idea to use another domain for longpolling only and send the message to the main domain. Sending should be fast and should not be hindered by waiting polls.
There should also be a way to end nicely the loop after some times (less than the specified JS timeout) because on your demo, my browsers (safari and firefox) both ends up with more than one long polling running. So, adding something like
$started = time();
while (… && (time() – $started) < 60) { … }
echo json_encode(….);
Would prevent memory leaks and ends nicely the query before starting a new one.
Anyway, thanks for the proof of concept !
Julian on May 21st, 2010
Just popping in to point out jQuery-JSONP 2.x has been released. The new implementation should limit the loading indicator problem.
Nice to see the plugin in use
admin on May 21st, 2010
Great idea Akeru! I’ve updated the chat.php file, it now has a configurable timeout – I’ve set it to a low value, (25sec), as that seems the most common value on shared hosts, you can of course set it much higher on your own host.
admin on May 21st, 2010
Excellent – the loading spinner is gone, thanks very much for updating your script!
Julian on May 22nd, 2010
Glad I could help
chx on May 27th, 2010
Be careful with this — you might pile up a ton of MySQL connections and each connection uses some MySQL buffers… you might want to close the MySQL connection before sleeping.
admin on May 28th, 2010
Are you suggesting that php automatically opens a mysql connection each time?