How to Implement Basic "Long Polling"

How do I implement basic Long Polling?

It's simpler than I initially thought.. Basically you have a page that does nothing, until the data you want to send is available (say, a new message arrives).

Here is a really basic example, which sends a simple string after 2-10 seconds. 1 in 3 chance of returning an error 404 (to show error handling in the coming Javascript example)

msgsrv.php

<?php
if(rand(1,3) == 1){
/* Fake an error */
header("HTTP/1.0 404 Not Found");
die();
}

/* Send a string after a random number of seconds (2-10) */
sleep(rand(2,10));
echo("Hi! Have a random number: " . rand(1,10));
?>

Note: With a real site, running this on a regular web-server like Apache will quickly tie up all the "worker threads" and leave it unable to respond to other requests.. There are ways around this, but it is recommended to write a "long-poll server" in something like Python's twisted, which does not rely on one thread per request. cometD is an popular one (which is available in several languages), and Tornado is a new framework made specifically for such tasks (it was built for FriendFeed's long-polling code)... but as a simple example, Apache is more than adequate! This script could easily be written in any language (I chose Apache/PHP as they are very common, and I happened to be running them locally)

Then, in Javascript, you request the above file (msg_srv.php), and wait for a response. When you get one, you act upon the data. Then you request the file and wait again, act upon the data (and repeat)

What follows is an example of such a page.. When the page is loaded, it sends the initial request for the msgsrv.php file.. If it succeeds, we append the message to the #messages div, then after 1 second we call the waitForMsg function again, which triggers the wait.

The 1 second setTimeout() is a really basic rate-limiter, it works fine without this, but if msgsrv.php always returns instantly (with a syntax error, for example) - you flood the browser and it can quickly freeze up. This would better be done checking if the file contains a valid JSON response, and/or keeping a running total of requests-per-minute/second, and pausing appropriately.

If the page errors, it appends the error to the #messages div, waits 15 seconds and then tries again (identical to how we wait 1 second after each message)

The nice thing about this approach is it is very resilient. If the clients internet connection dies, it will timeout, then try and reconnect - this is inherent in how long polling works, no complicated error-handling is required

Anyway, the long_poller.htm code, using the jQuery framework:

<html>
<head>
<title>BargePoller</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

<style type="text/css" media="screen">
body{ background:#000;color:#fff;font-size:.9em; }
.msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
.old{ background-color:#246499;}
.new{ background-color:#3B9957;}
.error{ background-color:#992E36;}
</style>

<script type="text/javascript" charset="utf-8">
function addmsg(type, msg){
/* Simple helper to add a div.
type is the name of a CSS class (old/new/error).
msg is the contents of the div */
$("#messages").append(
"<div class='msg "+ type +"'>"+ msg +"</div>"
);
}

function waitForMsg(){
/* This requests the url "msgsrv.php"
When it complete (or errors)*/
$.ajax({
type: "GET",
url: "msgsrv.php",

async: true, /* If set to non-async, browser shows page as "Loading.."*/
cache: false,
timeout:50000, /* Timeout in ms */

success: function(data){ /* called when request to barge.php completes */
addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
setTimeout(
waitForMsg, /* Request next message */
1000 /* ..after 1 seconds */
);
},
error: function(XMLHttpRequest, textStatus, errorThrown){
addmsg("error", textStatus + " (" + errorThrown + ")");
setTimeout(
waitForMsg, /* Try again after.. */
15000); /* milliseconds (15seconds) */
}
});
};

$(document).ready(function(){
waitForMsg(); /* Start the inital request */
});
</script>
</head>
<body>
<div id="messages">
<div class="msg old">
BargePoll message requester!
</div>
</div>
</body>
</html>

how does long polling work javascript?

The client cannot force the server to keep the connection open. The server is simply not closing the connection. The server will have to say at some point "that's it, there's no more content here, bye". In long polling, the server simply never does so and keeps the client waiting for more data, which it trickles out little by little as updates come in. That's long polling.

On the client side it's possible to check occasionally for the data which has already been received, while the request has not finished. That way data can occasionally be sent from the server over the same open connection. In your case this is not being done, the success callback will only fire when the request has finished. It's basically a cheap form of long polling in which the server keeps the client waiting for an event, sends data about this event and then closes the connection. The client takes that as the trigger, processes the data, then reconnects to the server to wait for the next event.

Long polling - Message system

Yes the way that you describe it is how the Long Polling Method is working generally.
Your sample code is a little vague, so i would like to add that you should do a sleep() for a small amount of time inside the while loop and each time compare the last_checked time (which is stored on server side) and the current time (which is what is sent from the client's side).

Something like this:

$current = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$last_checked = getLastCheckedTime(); //returns the last time db accessed

while( $last_checked <= $current) {
usleep(100000);
$last_checked = getLastCheckedTime();
}

$response = array();
$response['latestData'] = getLatestData() //fetches all the data you want based on time
$response['timestamp'] = $last_checked;
echo json_encode($response);

And at your client's side JS you would have this:

function longPolling(){
$.ajax({
type : 'Get',
url : 'data.php?timestamp=' + timestamp,
async : true,
cache : false,

success : function(data) {
var jsonData = eval('(' + data + ')');
//do something with the data, eg display them
timestamp = jsonData['timestamp'];
setTimeout('longPolling()', 1000);
},
error : function(XMLHttpRequest, textstatus, error) {
alert(error);
setTimeout('longPolling()', 15000);
}
});
}

AJAX long-polling : How do I make this code long-polling?

Note that the code below only tries to demonstrate how jQuery + PHP long-polling could work. And may have issues such as performance with concurrency, etc. I'm trying to demonstrate a simple long-polling script based on the question.

To be able to scale and support long-polling properly, I would suggest using technologies like Web Sockets that Darren mentioned, Socket.io, and possibly ReactPHP for alternative to node.js.

Client-side javascipt:

var updateMessages = function() {
var conv_id = [];
var lastMessages = [];

// your code to get conv_id and lastMessages here

$.ajax({
type: "POST",
url: 'create/populate',
timeout: 30000,
dataType: 'json',
data: {
'from': lastMessages,
'conv_id': conv_id
},
success: function(messages) {
// your success code here
},
error: function(xhr,textStatus,errorThrown) {
console.log(xhr.responseText);
},
complete: function() {
window.setTimeout(updateMessages, 2000);
},
});
};

updateMessages();

Your PHP controller:

public function populate()
{
$timeout_in_seconds = 20;
$interval_in_seconds = 5;

$start_time = time();
$timeout = false;

$timestamps = Input::get('from'); //timestamp of latest message
$conv_id = Input::get('conv_id');

// While we don't have any new messages, and haven't reached timeout yet.
while (empty($messages) && !$timeout) {

$allMessages = Messages::whereIn('conv_id', $conv_id)
->orderBy('created_at','desc');

if (empty($timestamps)) {
$messages = $allMessages->take(10)->get()->reverse();
} else {
asort($timestamps);
$messages = $allMessages->where('created_at', '>', end($timestamps))->get()->reverse();
}

$timeout = (time() - $start_time) > $timeout_in_seconds;

// If there is message or timeout, break out of the while loop and return the messages immediately.
if (!empty($messages) || $timeout) {
break;
}

sleep($interval_in_seconds);
}

return $messages;
}


Related Topics



Leave a reply



Submit