This is an introduction into a basic Comet setup with Nginx and jQuery. You will have to recompile Nginx with NGiNX_HTTP_Push_Module to enable the HTTP Push/Comet functionality. “NHPM” is based on the Basic HTTP Push Relay Protocol and turns Nginx into a very efficient Comet server. This simple recipe will enable you to create live asynchronous web applications utilizing long polling without the complexity of the Bayeux protocol.
First you will need to recompile Nginx with NHPM. If you run into dependency problems please review the Nginx Install Options.
NGINX_PUSH_NAME=nginx_http_push_module-0.692 NGINX_NAME=nginx-0.7.67 cd /usr/local/src/archive wget http://pushmodule.slact.net/downloads/$NGINX_PUSH_NAME.tar.gz wget http://nginx.org/download/$NGINX_NAME.tar.gz cd .. tar zxvf archive/$NGINX_PUSH_NAME.tar.gz tar zxvf archive/$NGINX_NAME.tar.gz cd $NGINX_NAME ./configure --prefix=/usr/local/$NGINX_NAME \ --add-module=/usr/local/src/$NGINX_PUSH_NAME make sudo make install
Next we will configure a virtual host in Nginx to enable our publisher and subscriber on a single channel called “cheetah”. This configuration allows the queue to hold up to 10 items, and those items will timeout after 5 seconds.
server { listen 81; server_name cheetah.example.com; root /var/www/cheetah.example.com; location /cheetah { push_channel_group pushmodule_cheetah; location /cheetah/pub { set $push_channel_id cheetah; push_publisher; push_message_timeout 5s; # Give the clients time push_message_buffer_length 10; # to catch up } location /cheetah/sub { set $push_channel_id cheetah; push_subscriber; send_timeout 3600; } } }
sudo /usr/local/nginx-0.7.67/sbin/nginx
Here is the code for the subscriber that will listen for incoming messages and simply display them. Notice the the use of “If-None-Match” and “If-Modified-Since”. These headers are used to traverse the messages in the queue. The ETAG header is used to uniquely identify messages with the same modification time.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>Listen</title> <script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript"> /* <![CDATA[ */ google.load("jquery", "1.4.2"); function listen(last_modified, etag) { $.ajax({ 'beforeSend': function(xhr) { xhr.setRequestHeader("If-None-Match", etag); xhr.setRequestHeader("If-Modified-Since", last_modified); }, url: '/cheetah/sub', dataType: 'text', type: 'get', cache: 'false', success: function(data, textStatus, xhr) { etag = xhr.getResponseHeader('Etag'); last_modified = xhr.getResponseHeader('Last-Modified'); div = $('<div class="msg">').text(data); info = $('<div class="info">').text('Last-Modified: ' + last_modified + ' | Etag: ' + etag); $('#data').prepend(div); $('#data').prepend(info); /* Start the next long poll. */ listen(last_modified, etag); }, error: function(xhr, textStatus, errorThrown) { $('#data').prepend(textStatus + ' | ' + errorThrown); } }); }; google.setOnLoadCallback(function() { /* Start the first long poll. */ /* setTimeout is required to let the browser know the page is finished loading. */ setTimeout(function() { listen('Thu, 1 Jan 1970 00:00:00 GMT', '0'); }, 500); }); /* ]]> */ </script> <style type="text/css"> #data { margin: .5em; } #data .info { font-weight: bold; font-size: 14px; } #data .msg { white-space: pre; font-family: courier; font-size: 14px; margin-bottom: .5em; margin-left: .5em; } </style> </head> <body> <div id="data"></div> </body> </html>
This is the publisher that will put messages into the queue.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>Send</title> <script type="text/javascript" src="http://www.google.com/jsapi"></script> <script type="text/javascript"> /* <![CDATA[ */ google.load("jquery", "1.4.2"); function showResult(status, response) { $('#result').html('<strong>status:</strong> ' + status + '<br /><strong>response:</strong><br />' + response); }; google.setOnLoadCallback(function() { $('#pub').submit(function() { message = $('#message').val(); /* Do not send empty message */ if (message == '') { return false; } $.ajax({ url: '/cheetah/pub', data: message, dataType: 'text', type: 'post', success: function(responseText, textStatus, xhr) { showResult(textStatus, responseText); }, error: function(xhr, textStatus, errorThrown) { showResult(textStatus, errorThrown); } }); return false; }); }); /* ]]> */ </script> </head> <body> <form id="pub" method="post" action="/cheetah/pub"> <input type="text" class="message" name="message" id="message" /> <input class="submit" type="submit" value="send" /> </form> <div id="result"></div></div> </body> </html>
http://cheetah.example.com:81/send.html
http://cheetah.example.com:81/listen.html
<?php $ch = curl_init('http://cheetah.example.com:81/cheetah/pub'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, "Hello World!"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $ret = curl_exec($ch); curl_close($ch);
curl -d 'Hello World' http://cheetah.example.com:81/cheetah/pub